算法
排序
影响程序效率的最大因素之一为: IO ------》输入和输出(读写文件)
-
内部排序
数据存储在内存中
插入排序
-
直接插入排序O(n2)
假设前方序列已经有序,将后方元素逐一插入前方,插入之后保证有序
循环从第一个元素开始,逐一向后
7 2 5 3 0 9 8 第一轮:(7)2 5 3 0 9 8 区间[0,0]有序 第二轮:(2 7)5 3 0 9 8 区间[0,1]有序 第三轮:(2 5 7)3 0 9 8 区间[0,2]有序 第四轮:(2 3 5 7)0 9 8 区间[0,3]有序 第五轮:(0 2 3 5 7)9 8 区间[0,4]有序 第六轮:(0 2 3 5 7 9)8 区间[0,5]有序 第七轮:(0 2 3 5 7 8 9) 区间[0,6]有序 完成!
代码实现如下:
#define SWAP(a,b) {(a) = (a) + (b); \ (b) = (a) - (b); \ (a) = (a) - (b);} void Direct_Insertion_Sort(int *arr) { for(int i = 1; i < sizeof(arr)/sizeof(arr[0]); i++){ for(int j = 0; j < i; j++){ if(arr[j] < arr[i]){ SWAP(arr[i], arr[j]); break; } } } } //或者换一种写法 void DIS(int *arr, size_t n) { int i, j; //由于第一个元素一定是有序的,因此不需要进行插入 for(i = 1; i < n; i++){ int key = arr[i];//记录需要插入的元素为key for(j = i-1; j >= 0 && arr[j] < arr[i]; --j){// arr[j+1] = arr[j]; } if(j+1 != i){ arr[j+1] = key; } } }
-
二分插入排序 O(n2)
使用折半的思想,快速找到位置,将后方元素逐一后移
7 2 5 3 0 9 8 第一轮:(7)2 5 3 0 9 8 区间[0,0]有序 第二轮:(2 7)5 3 0 9 8 区间[0,1]有序 第三轮:(2 5 7)3 0 9 8 区间[0,2]有序 第四轮:(2 3 5 7)0 9 8 区间[0,3]有序 第五轮:(0 2 3 5 7)9 8 区间[0,4]有序 第六轮:(0 2 3 5 7 9)8 区间[0,5]有序 第七轮:(0 2 3 5 7 8 9) 区间[0,6]有序 完成!
代码实现:
void Binary_Insertion_Sort(int *arr, size_t n) { int i, j; for(i = 1; i < n; i++){ int left = 0, right = i - 1; int key = arr[i]; while(left <= right){ int mid = (left + right)/2; if(arr[mid] <= key){ left = mid + 1; } else{ right = mid - 1; } } for(j = i-1; j > right; --j){// arr[j+1] = arr[j]; } arr[right + 1] = key; } }//数据量大了会令效果明显;
- 二路插入排序 空间复杂度O(n)
7 2 5 3 0 9 8 7 2 max min 5 7 2 max min 3 5 7 2 max min 3 5 7 0 2 max min 3 5 7 9 0 2 max min 3 5 7 8 9 0 2 max min
void Two_Way_Insertion_Sort(int *arr, size_t n) { int brr[n]; //需要把arr中元素二分插入到brr int max = 0, min = 0; brr[0] = arr[0]; for(int i = 1; i < n; i++){ if(arr[i] > brr[max]){ brr[++max] = arr[i]; } else if(arr[i] < brr[min]){ min = (min - 1 + (int)n)%n; brr[min] = arr[i]; } else if(arr[i] > brr[0]){ for(int j = max++; j >= 0 && brr[j] > arr[i]; --j){ brr[j+1] = brr[j]; } brr[j+1] = arr[i]; } else { for(int j = min++; j < n && brr[j] < arr[i]; ++j){ brr[j-1] = brr[j]; } brr[j-1] = arr[i]; } } for(int i = 0; i < n; i++){ arr[i] = brr[(i+min)%n] } }
-
希尔排序 O(nlog2n)
7 2 5 3 0 9 8 分组下标组合
代码实现如下:
void Shell_Sort(int *arr) { int step, i, j; for(step = n/2; step > 0; step /= 2){ for(i = step; i < n; i++){ int key = arr[i]; for(j = i - step; j > 0 && key < arr[j]; j -= step){ arr[j + step] = arr[j]; } if(j+step != i) arr[j+step] = key; } } }
选择排序
每次选择一个最大值或最小值
-
简单选择排序
每次遍历剩下的元素从中选取最大的或最小的元素和最后一个元素交换位置,循环n-1次,区间变小
时间复杂度为O(n2)
7 2 5 3 0 9 8 7 2 5 3 0 8 9 7 2 5 3 0 8 9 0 2 5 3 7 8 9 0 2 5 3 7 8 9 0 2 3 5 7 8 9
void Simple_Select_Sort(int *arr, size_t n) { int i, j; for(i = 1; i < n; i++){ //记录最大值的下标 int index = 0; for(j = 0; j < n-i; j++){ if(arr[index] = arr[j]){ index = j; } } if(index != n-i){ SWAP(arr[index], arr[n-i]); } } }
-
堆排序
一个结点的下标为i则其父结点的下标为i/2, 其子结点为2i ,2i+1
void reheap(int *arr, int pos, size_t n) { int key = arr[pos]; int child = 2*pos + 1; while(child < n){ if(child+1 < n && arr[child] < arr[child+1]){ ++child; } if(key < arr[child]){ arr[pos] = arr[child]; pos = child; child = 2*pos + 1; } else{ break; } } arr[pos] = key; } void Heap_Sort(int *arr, size_t n) { int i, j; for(i = n/2; i >= 0; i--){ reheap(arr, i, n); } for(i = (int)n-1; i > 0; --i){ SWAP(arr[0], arr[i]); reheap(arr, 0 ,i); } }
-
冒泡排序
相邻的两个元素进行比较
//老师说这是他觉得最没意思的排序 void Bubble_Sort(int *arr, size_tn) { int i, j, flag = 0;; for(i = 0; i < n-1; i++){ flag = 0; for(j = 1; j < n-i; j++){ if(arr[j] < arr[j-1]){ flag = 1; SWAP(arr[j], arr[j-1]); } } if(flag == 0){ break; } } }
-
鸡尾酒排序
同时选取最大值和最小值与区间开头和结尾的元素交换,逐渐做小区间即可得到有序数组
void Cocktail_Ordering(int *arr, size_t n) { int i, j; for(i = 0; i < n/2; ++i){ int min = i, max = i; for(j = i+1; j< n-i; ++j){ if(arr[j] < arr[min]){ min = j } else if(arr[max] < arr[j]){ max = j; } } if(min != i) SWAP(arr[min], arr[i]); if(max == i) max = min; if(max != n-i-1) SWAP(arr[max], arr[n-i-1]); } }
快速排序
核心思想:对于任何一个元素key都能找到一个位置,使其左边元素全部小于等于key
7 2 5 3 0 9 8
//对[left,right]区间进行排序 void quick_part_sort(int arr[],int left,int right){ if(left>=right) return; int key = arr[left]; int i=left,j=right; while(i<j){ while(i<j && key<=arr[j]) --j; arr[i] = arr[j]; //if(i!=j) while(i<j && arr[i]<=key) ++i; arr[j] = arr[i]; //if(i!=j) } arr[i] = key; if(i-1>left) quick_part_sort(arr,left,i-1); if(right>i+1) quick_part_sort(arr,i+1,right); } void quick_sort(int arr[],size_t n){ //quick_part_sort(arr,0,n-1); if(n<=1) return; int key = arr[0]; int left=0,right=n-1; while(left < right){//left == right结束循环 while(left<right && key <= arr[right]){ --right; } arr[left] = arr[right]; while(left<right && arr[left] <= key){ ++left; } arr[right] = arr[left]; } arr[left] = key; //[0,left-1] if(left>1){//左边有2个或者以上的元素 left quick_sort(arr,left); } //[left+1,n-1] //n-1>left+1 if(n>left+1){ quick_sort(arr+left+1,n-left-1); } }
归并排序(两路归并)
void merge_arr(int arr[],size_t n){ int mid = n/2; //[0,mid) 区间升序 [mid,n) 区间升序 进行合并 使得[0,n)区间都升序 int brr[mid];//用于存储[0,mid)部分数据 int i,j,k; for(i=0;i<mid;++i){ brr[i] = arr[i]; } //i用于记录放回去元素的下标位置 j记录brr的下标位置 k记录的是arr[mid-n)部分下标 i=0,j=0,k=mid; while(j<mid && k<n){//brr和arr[mid-n)两部分数据都还有 if(brr[j] < arr[k]){ arr[i++] = brr[j++]; } else { arr[i++] = arr[k++]; } } while(j<mid){ arr[i++] = brr[j++]; } } void merge_sort(int arr[],size_t n){ if(n<=1) return; int mid = n/2; //[0,mid) [mid,n) n==2 [0,1) [1,2) merge_sort(arr,mid); //[0,mid) 区间中的元素排序 merge_sort(arr+mid,n-mid); //[mid,n) 区间中的元素排序 merge_arr(arr,n); }
基数排序
思路:遍历所有元素找到最大值,根据其位数依次对个位、十位上的数字放入对应的坑内
用数组存储难以确定每个坑的大小,用单链表难以做到插入末尾,因此这里使用双向循环链表
132 456 34 673 8 99 105 126 577 12 46 个位 0 1 2 3 4 5 6 7 8 9 132 673 34 105 456 577 8 99 12 126 46 十位 0 1 2 3 4 5 6 7 8 9 105 12 126 132 46 456 673 99 8 34 577 百位 0 1 2 3 4 5 6 7 8 9 8 105 456 577 673 12 126 34 132 46 99 排序前:132 456 34 673 8 99 105 126 577 12 46 排序后:8 12 34 46 99 105 126 132 456 577 673
void Radix_Sort(int *arr, size_t n) { DLinkedList bucket[10]; int i,j; for(i=0;i<10;++i){ bucket[i] = create_dlinkedlist();为每个坑创建头结点 } size_t max = 0; for(i=0;i<n;++i){ if(max<arr[i]){ max = arr[i]; } } int div=1; for(;max!=0;max/=10,div*=10){ for(i=0;i<n;++i){ int b = arr[i]/div%10;//取个位 十位 百位 千位 push_back_dlinkedlist(bucket[b],&arr[i],sizeof(arr[i])); } for(i=0,j=0;i<10;++i){ while(!empty_dlinkedlist(bucket[i])){ //pop_front_dlinkedlist(bucket[i],&arr[j],sizeof(arr[j])); arr[j] = *(size_t *)front_dlinkedlist(bucket[i]); pop_front_dlinkedlist(bucket[i]); ++j; } } } for(i=0;i<10;++i){ destroy_dlinkedlist(bucket[i]); } }
桶子排序
- 把不同的数据放到不同的桶内,依次对桶子内的元素排序,然后顺序回收
计数排序
- 有100个100以内的整数,问用哪种块
- 在已知范围的情况下,建立范围内的每一个整数的数组,遍历后将遍历到元素的对应的位置数组值加1,遍历完成后,依次输出。
-
-
外部排序
数据存储在文件中,一次性无法将数据全部载入内存中进行操作,只能分批次加载,操作完成后写回程序
多路归并排序
-
二路归并
-
初始归并段
- 加载到内存中,用内部排序,排序后写回
- 选择置换序列来初始化初始归并段
-
败者树
-
最佳归并树
排序的一些性质
-
稳定性:
如果原始序列中有相同的元素所在的位置为(i, j)经过排序之后,这两个元素的位置为i’, j’ 且i’ < j’,则称该排序为稳定排序
排序方法 平均时间复杂度 最坏情况 空间复杂度 稳定性 直接插入 O(n2) O(n2) O(1) 稳定 二分插入 O(n) O(n2) O(1) 不稳定 二路插入 O(n2) O(n2) O(n) 稳定 希尔 O(nlog2n)/O(n1.3) O(nlog2n) O(1) 不稳定 简单选择/冒泡 O(n2) O(n2) O(1) 稳定 快速 O(nlog2n) O(n2) O(log2n)最坏为O(n) 不稳定 堆排序 O(nlog2n) O(nlog2n) O(1) 不稳定 归并排序 O(nlog2n) O(nlog2n) O(n) 稳定 基数排序 稳定 空间复杂度:申请额外动态内存(基于某个变量),或者定义非定长数组 或者递归调用(计算递归的深度)