手写排序查找
1、折半查找
思想:分治策略。把n个元素分成个数大致相同的两半,取a[n/2]与查找的key相比,一直搜索下去。
比如:总共有n个元素,每次查找的区间大小就是n,n/2,n/4,…,n/2^k(接下来操作元素的剩余个数),其中k就是循环的次数。
由于n/2^k取整后>=1,即令n/2^k=1,
可得k=log2n,(是以2为底,n的对数),所以时间复杂度可以表示O()=O(logn)
/*折半查找,非递归*/ int Binary_Search(int *a, int n, int key) //O(log n) { int low, mid, high; low = 0; high = n-1; while(low <= high) { mid = (low + high) / 2; if(a[mid] == key) return mid; if(a[mid] > key) high = mid - 1; if(a[mid] < key) low = mid + 1; } return 0; } /*折半查找,递归实现*/ int Binary_Search2(int *a, int low, int high, int key) { int mid = (low + high) / 2; if(a[mid] == key) return mid; if(a[mid] > key) return Binary_Search2(a, low, mid-1, key); //有没有return都可以 else return Binary_Search2(a, mid+1, high, key); //有没有return都可以 }
2、冒泡、选择、快速、堆
void bubblesort(int a[], int n) { for(int i=0; i<n-1; i++) for(int j=0; j<n-1-i; j++) if(a[j]>a[j+1]) swap(a[j],a[j+1]); } //或者 void bubblesort(int a[], int n) { for(int i=0; i<n; i++) for(int j=i; j<n; j++) if(a[i]>a[j]) swap(a[i],a[j]); } ————————————
比较次数:1+2+......+N-1
快速排序采用的思想是分治思想。
快速排序是找出一个元素(理论上可以随便找一个)作为基准(pivot),然后对数组进行分区操作,使基准左边元素的值都不大于基准值,基准右边的元素值 都不小于基准值,如此作为基准的元素调整到排序后的正确位置。递归快速排序,将其他n-1个元素也调整到排序后的正确位置。最后每个元素都是在排序后的正 确位置,排序完成。所以快速排序算法的核心算法是分区操作,即如何调整基准的位置以及调整返回基准的最终位置以便分治递归。
举例说明一下吧,这个可能不是太好理解。假设要排序的序列为
2 2 4 9 3 6 7 1 5 首先用2当作基准,使用i j两个指针分别从两边进行扫描,把比2小的元素和比2大的元素分开。首先比较2和5,5比2大,j左移
2 2 4 9 3 6 7 1 5 比较2和1,1小于2,所以把1放在2的位置
2 1 4 9 3 6 7 1 5 比较2和4,4大于2,因此将4移动到后面
2 1 4 9 3 6 7 4 5 比较2和7,2和6,2和3,2和9,全部大于2,满足条件,因此不变
经过第一轮的快速排序,元素变为下面的样子
[1] 2 [4 9 3 6 7 5]
之后,在把2左边的元素进行快排,由于只有一个元素,因此快排结束。右边进行快排,递归进行,最终生成最后的结果。
int quicksort(int a[], int left, int right) { if(left >= right){ return; } int key = a[left]; int low = left; int high = right; while(low < high) { while(low < high && a[high] > key) { high--; } a[low] = a[high]; while(low < high && a[low] < key) { low++; } a[high] = a[low]; } a[low] = key; quicksort(a,left,low-1); quicksort(a,low+1,right); } O(nlog2n)----O(n^2)
基本思想为(大顶堆):
1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无须区;
2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n];
3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
最好以及最坏都是O(nlog2n),空间复杂度O(1).
void AdjustHeap(int arr[], int start, int end) { //建立父节点指标和子节点指标 int dad = start; int son = dad * 2 + 1; while (son <= end) //若子节点指标在范围内才做比较 { if (son + 1 <= end && arr[son] < arr[son + 1]) //先比较两个子节点大小,选择最大的 son++; if (arr[dad] > arr[son]) //如果父节点大於子节点代表调整完毕,直接跳出函数 return; else //否则交换父子内容再继续子节点和孙节点比较 { swap(arr[dad], arr[son]); dad = son; son = dad * 2 + 1; } } } void heap_sort(int arr[], int len) { //初始化,i从最後一个父节点开始调整 for (int i = len / 2 - 1; i >= 0; i--) AdjusHeap(arr, i, len - 1); //先将第一个元素和已经排好的元素前一位做交换,再从新调整(刚调整的元素之前的元素),直到排序完毕 for (int i = len - 1; i > 0; i--) { swap(arr[0], arr[i]); AdjustHeap(arr, 0, i - 1); } } void main() { int arr[] = { 3, 0, 8, 6, 1, 5, 2, 6 }; int len = (int)sizeof(arr) / sizeof(*arr); heap_sort(arr, len); for (int i = 0; i < len; i++) cout << arr[i] << ' '; cout << endl; }
下面演示最大堆:父节点大于等于子节点
输入:4 10 3 5 1
构建堆的过程:
4
10 3
5 1
构建堆完成,现在需要变为大顶堆:
- 从下往上,10已经是大于其子节点了,不需改动。往上,4小于10,交换位置:
此时:10 4 3 5 1
堆为: 10
4 3
5 1
- 从下往上,4小于5,交换位置:
此时: 10 5 3 4 1
堆为: 10
5 3
4 1
- 交换根节点和最小(最后的一个,并不是数值上最小)的结点,并删除:
此时: 1 5 3 4 10
堆为: 1 ------- 1
5 3 5 3
4 10 4
- 删除以后,继续构建最大堆。从下往上,1和5换位置(看上图):
此时: 5 1 3 4 10
堆为: 5
1 3
4
- 从下往上,1小于4,交换位置:
此时: 5 4 3 1 10
堆为: 5
4 3
1
- 交换根节点和最小结点,并删除:
此时: 1 4 3 5 10
堆为: 1 ------- 1
4 3 4 3
5
- 从下往上,继续构建最大堆。1和4交换位置:
此时: 4 1 3 5 10
堆为: 4
1 3
- 交换根节点和最后一个结点,并删除(4和3交换,删掉4):
此时: 3 1 4 5 10
堆为: 3 --------- 3
1 4 1
- 剩下已是最大堆,不需构建,直接交换结点:
此时: 1 3 4 5 10
========附录============
关于topK问题就不得不说了,类型有,找出前K个大的数、找出第K个大的数、找出重复K次的数
/* topK算法实现 */ #include <stdio.h> /* 调整小顶堆,pos:唯一违反堆性质的点的位置 */ void heapsort(int *a, const int len, int pos) { int min; int child = (pos * 2) + 1;// 左孩子 while (1) { if (child + 1 < len)// 有两个孩子 { min = a[child] < a[child + 1] ? a[child] : a[++child]; } else if (child + 1 == len) // 只有左孩子 { min = a[child]; } else break; // 数组结束,调整完成 if (a[pos] > min) { a[child] = a[pos]; a[pos] = min; pos = child; // 更新父节点索引 child = (pos * 2) + 1; // 下一个调整点 } else break; // 已经是堆,无需调整 } } /* 建立小顶堆 */ void Create(int *b, const int k) { int i; for (i = k / 2 -1; i >= 0; --i) { heapsort(b, k, i); } } /* 选出数组中最大的k个数,存入数组arr_k中 */ void top_k(const int *a,const int len, int *b, int k) { int i; for (i = 0; i < k; ++i) b[i] = a[i]; Create(b, k); // 用a的前k个数建堆 for (; i < len; ++i) { b[0] = a[i]; heapsort(b, k, 0); } } /* 测试代码 */ int main() { int a[] = { 8, 1, 2, 7, 3, 4, 5, 6, 9 }; int K 4; int i; int b[K]; top_k(a, 9, b, K); for (i = 0; i < K; ++i) { printf("%d ", b[i]); } return 0; }