测试二分查找、直接选择排序等算法的时间复杂度

        一、二分查找
        二分查找,又称折半查找,进行二分查找的前提是,该线性表或者树已经排好序,这里以线性表为例,假设线性表已经按关键字有序排列,且为递增有序排列。
        二分查找的思路:设线性表R[left, right]是当前的查找区间,首先确定该区间的中点位置mid = (left+right)/2, 然后将待查的K值与R[mid].key进行比较,分别如下:
        1)若K = R[mid].key, 则直接返回该元素的下标;
        2)若K < R[mid].key, 则
            left = mid-1;
            mid = (left+right)/2;
      在左半区间[left, mid-1]查找;
        3) 若K > R[mid].key, 则
            right = mid+1;
            mid = (left+right)/2;
      在右半区间[left, mid-1]查找;
    4) 循环第2) 步 和第3)步,直到left > right为止,即查找空间变为0为止,若找到就返回该元素的下标,否则返回-1。
    二分查找,每查找一次,其搜索区间就缩小一半,因此算法的速度非常快,时间复杂度为 O ( l o g   n ) O(log \,n) O(logn)
    //二分查找 binarySearch()

	int binarySearch(int arr[], int n, int target) {
    
    
		int left = 0, right = n-1;
		while (left <= right) {
    
    
			int mid = (left+right)/2;
			if(arr[mid] == target)
				return mid;

			if(arr[mid] > target)
				right = mid - 1;
			else
				left = mid + 1;
		}
		return -1;
	}

    二、选择排序
    选择排序的基本方法是:每步从待排序的记录中选出关键字最小的记录,顺序放在已排序的记录的最后,直到全部排完为止。选择排序包括直接选择排序和堆排序。这里介绍直接选择排序。
    直接选择排序的过程是:假设记录放在R[0,n-1]之中,R[0,i-1]是有序区,R[i,n-1]是无序区,且有序区所有关键字均小于无序区所有关键字。需要将R[i]添加到R[0,i-1]之中,使R[0,i]成为有序的。这里,每一趟从无序区R[i,n-1]中选择一个最小关键字的记录R[k],使其与R[i]进行交换,显然R[0,i]就变成了新的有序区。
    2.1) 使用结构体,来表示直接选择排序


#define MaxSize 100
typedef int KeyType;
typedef char ElemType;
typedef struct{
    
    
	KeyType  key;       //关键字域
	ElemType data[10];  //其他数据域
}SqList;

void SelectSort(SqList R[], int n) {
    
    
	int i, j, k;
	SqList tmp;
	for (i = 0; i < n - 1; j++) {
    
    
		k = i;
		for (j = i + 1; j < n; j++) {
    
    
			if (R[j].key < R[k].key)
				k = j;
		}

		tmp = R[i];
		R[i] = R[k];
		R[k] = tmp;
	}
}

    2.2 使用数组,来表示直接选择排序

//交换a和b 方法一
void swapValue(int &a, int &b) {
    
    
	int tmp = a;
	a = b;
	b = tmp;
}

//交换a和b 方法二
void swapValue2(int &a, int &b) {
    
    
	a = a + b;
	b = a - b;
	a = a - b;	
}


//直接选择排序
void selectionSort(int arr[], int n) {
    
    
	for (int i = 0; i < n - 1; i++) {
    
    
		int minIndex = i;
		for (int j = i + 1; j < n; j++) {
    
    
			if (arr[j] < arr[minIndex]) {
    
    
				minIndex = j;
			}
		}
		swapValue(arr[i], arr[minIndex]);
	}
}

    三、编写算法的测试用例
    3.1 将算法都放在一个命令空间中,比如namespace MyAlgorithmTester,它是一个头文件:MyAlgorithm.h
    //MyAlgorithm.h

#ifndef MY_ALGORITHM_H
#define MY_ALGORITHM_H

#include <iostream>
#include <cassert>
#include <cmath>

using namespace std;

#define min(a,b)  (((a)<(b))?(a):(b))

namespace MyAlgorithmTester {
    
    

	//O(logN)
	int binarySearch(int arr[], int n, int target) {
    
    
		int left = 0, right = n-1;
		while (left <= right) {
    
    
			int mid = (left+right)/2;
			if(arr[mid] == target)
				return mid;

			if(arr[mid] > target)
				right = mid - 1;
			else
				left = mid + 1;
		}
		return -1;
	}

	//O(N)
	int findMax(int arr[],int n) {
    
    
		assert(n > 0);
		int res = arr[0];
		for (int i=1;i<n;i++) {
    
    
			if(arr[i] > res)
				res = arr[i];
		}

		return res;
	}

	//O(NlogN)
	void __merge(int arr[],int left,int mid,int right, int aux[]) {
    
    
		int i = left,j = mid+1;
		for(int k=left; k<= right; k++) {
    
    
			if (i > mid) {
    
    
				arr[k] = aux[j];
				j++;
			}
			else if (j > right) {
    
    
				arr[k] = aux[i];
				i++;
			}
			else if (aux[i] < aux[j]) {
    
    
				arr[k] = aux[i];
				i++;
			}
			else {
    
    
				arr[k] =aux[j];
				j++;
			}		
		}
	}

	//自顶向上排序
	void mergeSort(int arr[], int n) {
    
    
		int *aux = new int[n];
		for (int i=0;i<n;i++) {
    
    
			aux[i] = arr[i];
		}

		for (int sz=1;sz<n;sz += sz) {
    
    
			for (int i=0; i < n; i += sz+sz) {
    
    
				__merge(arr,i,i+sz-1,min(i+sz+sz-1, n-1), aux);
			}
		}

		delete[] aux;
		return;
	}

	//O(N^2)
	void selectionSort(int arr[], int n) {
    
    
		for (int i=0;i<n-1; i++) {
    
    
			int minIndex = i;
			for (int j=i+1; j<n; j++) {
    
    
				if(arr[j] < arr[minIndex])
					minIndex = j;
			}
			swap(arr[i],arr[minIndex]);
		}
	}


}

#endif

    3.2) 将生成数据集的函数放在MyUtil命名空间中,也是一个头文件: MyUtil.h
    //MyUtil.h

#ifndef MY_UTIL_H
#define MY_UTIL_H

#include <iostream>
#include <cassert>
#include <ctime>
using namespace std;

namespace MyUtil {
    
    
	int *generateRandomArray(int n, int rangeL, int rangeR) {
    
    
		assert(n > 0 && rangeL <= rangeR );

		int *arr = new int[n];
		srand(time(NULL));
		for (int i = 0; i < n; i++) {
    
    
			arr[i] = rand() % (rangeR - rangeL + 1) + rangeL;
		}
		return arr;
	}

	int *generateOrderedArray(int n) {
    
    
		assert(n > 0);

		int *arr = new int[n];
		for (int i = 0; i < n; i++) {
    
    
			arr[i] = i;
		}
		return arr;
	}

}


#endif

    3.3) 编写主函数
    //main.cpp

#include "MyAlgorithm.h"
#include "MyUtil.h"

#include <cmath>
#include <ctime>
#include <windows.h>
using namespace std;

int main() {
    
    

	//1) findMax 数据规模倍乘测试 
	//O(n)
	//for (int i = 10; i <= 26; i++) {
    
    
	//	int n = pow(2, i);
	//	int *arr = MyUtil::generateRandomArray(n, 0, 100000000);

	//	clock_t startTime = clock();
	//	MyAlgorithmTester::findMax(arr, n);
	//	clock_t endTime = clock();

	//	cout << "data size 2^" << i << " = " << n << "\t";
	//	cout << "Time cost: " << double(endTime - startTime) / CLOCKS_PER_SEC << endl;

	//	delete[] arr;
	//}

	//2) selectionSort 数据规模倍乘测试 
	//O(n^2)
	//cout << "Test for selectionSort:" << endl;
	//for (int i = 10; i <= 15; i++) {
    
    
	//	int n = pow(2, i);
	//	int *arr = MyUtil::generateRandomArray(n, 0, 100000000);

	//	clock_t startTime = clock();
	//	MyAlgorithmTester::selectionSort(arr, n);
	//	clock_t endTime = clock();

	//	cout << "data size 2^" << i << " = " << n << "\t";
	//	cout << "Time cost: " << double(endTime - startTime) / CLOCKS_PER_SEC << endl;

	//	delete[] arr;
	//}

	//3) binarySearch 数据规模倍乘测试 
	//O(logn)
	//cout << "Test for selectionSort:" << endl;
	//double run_time = 0;
	//LARGE_INTEGER time_start;
	//LARGE_INTEGER time_end;
	//double dqFreq;
	//LARGE_INTEGER freq;
	//QueryPerformanceFrequency(&freq);
	//dqFreq = (double)freq.QuadPart;

	//for (int i = 10; i <= 27; i++) {
    
    
	//	int n = pow(2, i);
	//	int *arr = MyUtil::generateRandomArray(n, 0, 100000000);		

	//	QueryPerformanceCounter(&time_start);
	//	MyAlgorithmTester::binarySearch(arr, n, 0);
	//	QueryPerformanceCounter(&time_end);
	//	run_time = 1000000 * (time_end.QuadPart - time_start.QuadPart) / dqFreq;
	//	cout << "data size 2^" << i << " = " << n << "\t";
	//	cout << "Time cost: " << run_time << endl;

	//	delete[] arr;
	//}

	//4) binarySearch 数据规模倍乘测试 
	//O(logn)
	cout << "Test for selectionSort:" << endl;
	double run_time = 0;
	LARGE_INTEGER time_start;
	LARGE_INTEGER time_end;
	double dqFreq;
	LARGE_INTEGER freq;
	QueryPerformanceFrequency(&freq);
	dqFreq = (double)freq.QuadPart;

	for (int i = 10; i <= 26; i++) {
    
    
		int n = pow(2, i);
		int *arr = MyUtil::generateRandomArray(n, 0, 100000000);

		QueryPerformanceCounter(&time_start);
		MyAlgorithmTester::mergeSort(arr, n);
		QueryPerformanceCounter(&time_end);
		run_time = 1000 * (time_end.QuadPart - time_start.QuadPart) / dqFreq;
		cout << "data size 2^" << i << " = " << n << "\t";
		cout << "Time cost: " << run_time << endl;

		delete[] arr;
	}



	system("pause");

	return 0;
}

    3.3 ) 算法说明
    a) findMax() 查找数组中的最大元素。
    该算法,当数据量增大一倍,算法耗时也增大一倍,说明该算法的时间复杂度是线性的,即findMax()的时间复杂度为 O ( n ) O(n) O(n)

图(1) findMax() 查找最大元素算法

    b) selectionSort() 对数组进行直接选择排序
    该算法,当数据量增大一倍,算法耗时变为原来的4倍,说明该算法的时间复杂度是一个指数类型的,指数为 l o g 2 4 = 2 log_2{4} = 2 log24=2, 即selectionSort()算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2)

图(2) selectionSort() 直接选择排序算法

    c) binarySearch() 对数组进行二分查找
    该算法,当数据量增大一倍,算法耗时增加的很少,几乎没有增加,可以推测该算法的时间复杂度为 O ( l o g   n ) O(log \,n) O(logn)

图(3) binarySearch() 二分查找算法

    c) mergeSort() 对数组进行归并排序
    该算法,当数据量增大一倍,算法耗时增加2倍多一点,说明该算法的复杂度介于 O ( n ) O(n) O(n) ~ O ( n 2 ) O(n^2) O(n2) 之间 ,可以推测该算法的时间复杂度为 O ( n l o g   n ) O(nlog \,n) O(nlogn)

图(4) mergeSort() 归并排序算法

        参考文献:
        1. 数据结构习题与解析B级,李春葆,2006

扫描二维码关注公众号,回复: 11850565 查看本文章

猜你喜欢

转载自blog.csdn.net/sanqima/article/details/107578007
今日推荐