一、二分查找
二分查找,又称折半查找,进行二分查找的前提是,该线性表或者树已经排好序,这里以线性表为例,假设线性表已经按关键字有序排列,且为递增有序排列。
二分查找的思路:设线性表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) 。
b) selectionSort() 对数组进行直接选择排序
该算法,当数据量增大一倍,算法耗时变为原来的4倍,说明该算法的时间复杂度是一个指数类型的,指数为
l
o
g
2
4
=
2
log_2{4} = 2
log24=2, 即selectionSort()算法的时间复杂度为
O
(
n
2
)
O(n^2)
O(n2) 。
c) binarySearch() 对数组进行二分查找
该算法,当数据量增大一倍,算法耗时增加的很少,几乎没有增加,可以推测该算法的时间复杂度为
O
(
l
o
g
n
)
O(log \,n)
O(logn) 。
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) 。
参考文献:
1. 数据结构习题与解析B级,李春葆,2006