http://space.itpub.net/15203236/viewspace-616582
http://student.zjzk.cn/course_ware/data_structure/web/paixu/paixu8.1.1.1.htm
把内排序分为:插入排序、交换排序、选择排序和归并排序。
- 插入排序(Insertion Sort)的基本思想是:每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子文件中的适当位置,直到全部记录插入完成为止。 本节介绍两种插入排序方法:直接插入排序和希尔排序(分组插入排序,直到增量为1)。
希尔排序基本思想
基本思想:
先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为dl的倍数的记录放在同一个组中。先在各组内进行直接插人排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
该方法实质上是一种分组插入方法。
希尔排序的时间性能优于直接插入排序的原因:
①当文件初态基本有序时直接插入排序所需的比较和移动次数均较少。
②当n值较小时,n和n2的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度0(n2)差别不大。
③在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。
因此,希尔排序在效率上较直接插人排序有较大的改进。
- 交换排序的基本思想是:两两比较待排序记录的关键字,发现两个记录的次序相反时即进行交换,直到没有反序的记录为止。 应用交换排序基本思想的主要排序方法有:冒泡排序和快速排序。
- 选择排序(Selection Sort)的基本思想是:每一趟从待排序的记录中选出关键字最小的记录,顺序放在已排好序的子文件的最后,直到全部记录排序完毕。 常用的选择排序方法有直接选择排序和堆排序。
直接选择排序中,为了从R[1..n]中选出关键字最小的记录,必须进行n-1次比较,然后在R[2..n]中选出关键字最小的记录,又需要做n-2次比较。事实上,后面的n-2次比较中,有许多比较可能在前面的n-1次比较中已经做过,但由于前一趟排序时未保留这些比较结果,所以后一趟排序时又重复执行了这些比较操作。
堆排序可通过树形结构保存部分比较结果,可减少比较次数。
- 分配排序的基本思想:排序过程无须比较关键字,而是通过"分配"和"收集"过程来实现排序.它们的时间复杂度可达到线性阶:O(n)。
箱排序(Bin Sort)
1、箱排序的基本思想
https://www.cnblogs.com/my_life/articles/5097914.html
箱排序也称桶排序(Bucket Sort),其基本思想是:设置若干个箱子,依次扫描待排序的记录R[0],R[1],…,R[n-1],把关键字等于k的记录全都装入到第k个箱子里(分配),然后按序号依次将各非空的箱子首尾连接起来(收集)。
按照一定的算法,把数字分到不同的组,然后在每个非空的桶中进行快速排序,然后收集。
【例】要将一副混洗的52张扑克牌按点数A<2<…<J<Q<K排序,需设置13个"箱子",排序时依次将每张牌按点数放入相应的箱子里,然后依次将这些箱子首尾相接,就得到了按点数递增序排列的一副牌。
void bucketSort(vector<int> &a, int bucketSize) { int len = a.size(); if (len < 2) return; int Min = a[0], Max = a[0]; for (int i = 1; i < len; i++) { Max = max(Max, a[i]); Min = min(Min, a[i]); } int bucketCount = (Max - Min) / bucketSize + 1; //这个区间是max-min+1,但是我们要向上取整,就是+bucketSize-1,和上面的形式是一样的 vector<int> bucketArr[bucketCount]; for (int i = 0; i < len; i++) { bucketArr[(a[i] - Min) / bucketSize].push_back(a[i]); } a.clear(); for (int i = 0; i < bucketCount; i++) { int tlen = bucketArr[i].size(); sort(bucketArr[i].begin(),bucketArr[i].end()); for (int j = 0; j < tlen; j++) a.push_back(bucketArr[i][j]); } }
按照算法的复杂度分为两大类,冒泡排序、简单选择排序和直接插入排序属于简单算法,而希尔排序、堆排序、归并排序、快速排序属于改进算法。
希尔排序是D.L.Shell于1959年提出来的一种排序算法,在这之前排序算法的时间复杂度基本都是O(n2)的,希尔排序算法是突破这个时间复杂度的第一批算法之一。将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。
归并排序(Merging Sort)就是利用归并的思想实现的排序方法。它的原理是假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到⌈n/2⌉(⌈x⌉表示不小于x的最小整数)个长度为2或1的有序子序列;再两两归并,……,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。
1 快速排序(QuickSort) (分而治之)
快速排序是一个就地排序,分而治之,大规模递归的算法。从本质上来说,它是归并排序的就地版本。快速排序可以由下面四步组成。
(1) 如果不多于1个数据,直接返回。
(2) 一般选择序列最左边的值作为支点数据。
(3) 将序列分成2部分,一部分都大于支点数据,另外一部分都小于支点数据。
(4) 对两边利用递归排序数列。
快速排序比大部分排序算法都要快。尽管我们可以在某些特殊的情况下写出比快速排序快的算法,但是就通常情况而言,没有比它更快的了。快速排序是递归的,对于内存非常有限的机器来说,它不是一个好的选择。
int quicksort(vector<int> &v, int left, int right){
if(left < right){
int key = v[left];
int low = left;
int high = right;
while(low < high){
while(low < high && v[high] > key){
high--;
}
v[low] = v[high];
while(low < high && v[low] < key){
low++;
}
v[high] = v[low];
}
v[low] = key;
quicksort(v,left,low-1);
quicksort(v,low+1,right);
}
}
2 归并排序(MergeSort)
归并排序先分解要排序的序列,从1分成2,2分成4,依次分解,当分解到只有1个一组的时候,就可以排序这些分组,然后依次合并回原来的序列中,这样就可以排序所有数据。合并排序比堆排序稍微快一点,但是需要比堆排序多一倍的内存空间,因为它需要一个额外的数组。
3 堆排序(HeapSort)
我们前面讲到简单选择排序,它在待排序的n个记录中选择一个最小的记录需要比较n-1次。本来这也可以理解,查找第一个数据需要比较这么多次正常的,否则如何知道它是最小的记录。
可惜的是,这样的操作并没有把每一趟的比较结果保存下来,在后一趟的比较中,有许多比较在前一趟已经做过了,但由于前一趟排序时未保存这些比较结果,所以后一趟排序时又重复执行了这些比较操作,因而记录的比较次数较多。
如果可以做到每次在选择到最小的记录的同时,并根据比较对其他记录做出相应的调整,那样排序的总体效率就会非常高了。而堆排序(Heap Sort),就是对简单选择排序进行的一种改进,这种改进的效果是非常明显的。
堆排序适合于数据量非常大的场合(百万数据)。
堆排序不需要大量的递归或者多维的暂存数组。这对于数据量非常巨大的序列是合适的。比如超过数百万条记录,因为快速排序,归并排序都使用递归来设计算法,在数据量非常大的时候,可能会发生堆栈溢出错误。
堆排序会将所有的数据建成一个堆,最大的数据在堆顶,然后将堆顶数据和序列的最后一个数据交换。接下来再次重建堆,交换数据,依次下去,就可以排序所有的数据。
堆排序的算法步骤如下:
-
把无序数列构建成二叉堆;
-
循环删除堆顶元素,替换到二叉堆的末尾,调整堆产生新的堆顶。
4 Shell排序(ShellSort)(选择一个间隔,分组进行插入排序)
Shell排序通过将数据分成不同的组,先对每一组进行排序,然后再对所有的元素进行一次插入排序,以减少数据交换和移动的次数。平均效率是O(nlogn)。其中分组的合理性会对算法产生重要的影响。现在多用D.E.Knuth的分组方法。
Shell排序比冒泡排序快5倍,比插入排序大致快2倍。Shell排序比起QuickSort,MergeSort,HeapSort慢很多。但是它相对比较简单,它适合于数据量在5000以下并且速度并不是特别重要的场合。它对于数据量较小的数列重复排序是非常好的。
5 插入排序(InsertSort)(打扑克牌,理牌)
插入排序通过把序列中的值插入一个已经排序好的序列中,直到该序列的结束。插入排序是对冒泡排序的改进。它比冒泡排序快2倍。一般不用在数据大于1000的场合下使用插入排序,或者重复排序超过200数据项的序列。
printline("before sort:", v);
for (int i=1; i<v.size(); i++){
int key = v[i];
int j = i-1;
while (j >= 0 && v[j] > key){
v[j+1] = v[j];
j--;
}
v[j+1] = key;
}
printline("after sort:", v);
6 冒泡排序(BubbleSort)
冒泡排序是最慢的排序算法。在实际运用中它是效率最低的算法。它通过一趟又一趟地比较数组中的每一个元素,使较大的数据下沉,较小的数据上升。它是O(n^2)的算法。
7 交换排序(ExchangeSort)和选择排序(SelectSort)
这两种排序方法都是交换方法的排序算法,效率都是 O(n2)。在实际应用中处于和冒泡排序基本相同的地位。它们只是排序算法发展的初级阶段,在实际中使用较少。
选择排序:
for(int i=0; i<v.size(); i++){
int min = v[i];
int temp;
int index = i;
for(int j=i+1;j<v.size();j++){
if(v[j] < min){
min = v[j];
index = j;
}
}
temp = v[i];
v[i] = min;
v[index]= temp;
}
8 基数排序(RadixSort)
基数排序和通常的排序算法并不走同样的路线。它是一种比较新颖的算法,但是它只能用于整数的排序,如果我们要把同样的办法运用到浮点数上,我们必须了解浮点数的存储格式,并通过特殊的方式将浮点数映射到整数上,然后再映射回去,这是非常麻烦的事情,因此,它的使用同样也不多。而且,最重要的是,这样算法也需要较多的存储空间。
基数排序(Radix sort) 是一种比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
void RadixSortSort(vector<int> &a) { int len = a.size(); if (len < 2) return; int Max = a[0]; for (int i = 1; i < len; i++) { Max = max(Max, a[i]); } int maxDigit = log10(Max) + 1; //直接使用log10函数获取位数,这样的话就不用循环了,这里被强制转换是向下取整 int mod = 10, div = 1; vector<int> bucketList[10]; for (int i = 0; i < maxDigit; i++, mod *= 10, div *= 10) { for (int j = 0; j < len; j++) { int num = (a[j] % mod) / div; bucketList[num].push_back(a[j]); } int index = 0; for (int j = 0; j < 10; j++) { int tlen=bucketList[j].size(); for (int k = 0; k < tlen; k++) a[index++] = bucketList[j][k]; bucketList[j].clear(); } } }
基数排序 、 桶排序 、 计数排序 原理都差不多,都借助了 “桶” 的概念,但是使用方式有明显的差异,其差异如下:
-
基数排序:根据键值的每位数字来分配桶;
-
桶排序:每个桶存储一定范围的数值;
-
计数排序:每个桶只存储单一键值。
9 总结
下面是一个总的表格,大致总结了我们常见的所有的排序算法的特点。
排序法 | 平均时间 | 最差情形 | 稳定度 | 额外空间 | 备注 |
冒泡 | O(n2) | O(n2) | 稳定 | O(1) | n小时较好 |
交换 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
选择 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
插入 | O(n2) | O(n2) | 稳定 | O(1) | 大部分已排序时较好 |
基数 | O(logRB) | O(logRB) | 稳定 | O(n) | B是真数(0-9), R是基数(个十百) |
Shell | O(nlogn) | O(ns) 1<2 | 不稳定 | O(1) | s是所选分组 |
快速 | O(nlogn) | O(n2) | 不稳定 | O(nlogn) | n大时较好 |
归并 | O(nlogn) | O(nlogn) | 稳定 | O(1) | n大时较好 |
堆 | O(nlogn) | O(nlogn) | 不稳定 | O(1) | n大时较好 |
========
https://www.cnblogs.com/BobHuang/p/11263183.html
dp其实就是在用空间去换取时间
常见时间复杂度的 “大O表示法” 描述有以下几种:
时间复杂度 | 非正式术语 |
---|---|
O(1) | 常数阶 |
O(n) | 线性阶 |
O(n2) | 平方阶 |
O(log n) | 对数阶 |
O(n log n) | 线性对数阶 |
O(n3) | 立方阶 |
O(2n) | 指数阶 |
一个算法在N规模下所消耗的时间消耗从大到小如下:
O(1) < O(log n) < O(n) < O(n log n) < O(n2) < O(n3) < O(2n)
指数级的增长是非常快的
常见的排序算法
根据时间复杂度的不同,常见的算法可以分为3大类。
1.O(n²) 的排序算法
-
冒泡排序
-
选择排序
-
插入排序
2.O(n log n) 的排序算法
- 希尔排序
-
归并排序
-
快速排序
-
堆排序
3.线性的排序算法
-
计数排序
-
桶排序
-
基数排序
各种排序的具体信息
冒泡排序(Bubble Sort)
冒泡排序(Bubble Sort) 是一种基础的 交换排序。
//两两比较,大的后移,移动最后
//稳定:因为相邻的两个数如果相等的话,不会做交换
插入排序:打扑克
希尔排序【分组的插入排序】
#include <iostream> using namespace std; void InsertSort(int arr[], int arr_size) { for(int i = 1; i < arr_size; ++i) { int tmp = arr[i]; int j = i - 1; for(; j > 0; --j) { if(tmp >= arr[j]) { break; } else { arr[j+1] = arr[j]; } } arr[j+1] = tmp; } } void InsertSort2(int arr[], int arr_size) { int start; int end; int temp=0; int mid,j; for(int i=1;i<arr_size;i++) { start=0; end=i-1; temp=arr[i]; while(start<=end) { mid=(start+end)/2; if (temp<arr[mid]) { end=mid-1; } else { start=mid+1; } } cout << start << ", " << end << endl; //while循环完后,start=end+1,此时start为当前插入数字所待坑位! //把坑位给当前插入的数据挪出来 /* for( j = i-1;j >= start;j-- ) { arr[j+1] = arr[j]; } */ for( j = i;j > start;j-- ) { arr[j] = arr[j-1]; } //将当前插入数字挪入它该待的坑位 arr[start] = temp; } } //二分排序 = 插入排序 + 二分查找 void InsertSort3(int arr[], int arr_size) { int start; int end; int temp=0; int mid,j; for(int i=1;i<arr_size;i++) //准备给i位进行插入排序 { start=0; //end=i-1; //此时前i-1位是已经排序好的, 所以作为end; end = i; //如果用左闭右开,end = i. temp=arr[i]; //待插入的数 while(start<end) //[左闭,右开) { mid=start + (end - start) / 2; //从闭区间向中点靠近 //mid = last - (last - first) // (左开,右闭],从右向左靠近 if (temp<arr[mid]) { end=mid; //左闭右开 } else { start=mid+1; } } } cout << start << ", " << end << endl; for( j = i;j > start;j-- ) //依次后移,给i腾位置 { arr[j] = arr[j-1]; } //将当前插入数字挪入它该待的坑位 arr[start] = temp; } } //两两比较,大的后移,移动最后 //稳定:因为相邻的两个数如果相等的话,不会做交换 void BubbleSort(int arr[], int arr_size) { for(int i = arr_size - 1; i >=0; --i) //从后往前,最后的是最大的 { for(int j = 0; j < i; ++j) //j不要超过i { if(arr[j] > arr[j+1]) { std::swap(arr[j], arr[j+1]); } } } } void BubbleSort2(int arr[], int arr_size) { for (int i = 0; i < arr_size - 1; i++) { for (int j = 0; j < arr_size - 1 - i; j++) //这里的两个for跟上面的BubbleSort一个意思,只不过不太好理解,j < arr_size -1 - i; 也是从后往前,最后的最大 { if (arr[j] > arr[j + 1]) { swap(arr[j], arr[j + 1]); //不满足偏序,交换 } } } } //选择最大的,放到最后;或者选择最小的,放在最前面。 void SelectSort(int arr[], int arr_size) { for(int i = arr_size - 1; i >=0; --i) //从后往前,最后的是最大的 { { int max_index = i; for(int j = 0; j < i; ++j) { if(arr[j] > arr[max_index]) { max_index = j; } } std::swap(arr[max_index], arr[i]); } } void SelectSort2(int arr[], int arr_size) { for (int i = 0, minIndex; i < arr_size - 1; i++) //从前往后,选择最小的,放到前面已有序的最后 { minIndex = i; //最小下标 for (int j = i + 1; j < arr_size; j++) //访问未排序的元素 { if (arr[j] < arr[minIndex]) minIndex = j; //找到最小的 } swap(arr[i], arr[minIndex]); } } //把剩下的未排序的数字插入到前面已拍好序的正确位置[从后向前比较,往后移],打扑克。 void InsertSortNew(int arr[], int arr_size) { for (int i = 1; i < arr_size; ++i) //第一个认为已排好序,从第二个开始进行插入排序;i:下一个待插入的数字 { int tmp = arr[i]; int j = i; //从i开始,向前找到合适的位置 for(; j > 0; --j) { if(tmp < arr[j-1]) //比arr[i]大的数字,依次后移 { arr[j] = arr[j-1]; } else { break; } } arr[j] = tmp; } } void ShellSort(int arr[], int arr_size) { } int main() { int arr[] = {1, 867, 76, 12, 40, 16, 678, 990, 1089, 431, 23, 68, 9, 3,2, 10, 6, 4, 7, 11, 20, 8, 6}; //InsertSort3(arr, sizeof(arr) / sizeof(arr[0])); //BubbleSort(arr, sizeof(arr) / sizeof(arr[0])); //BubbleSort2(arr, sizeof(arr) / sizeof(arr[0])); //SelectSort(arr, sizeof(arr) / sizeof(arr[0])); InsertSortNew(arr, sizeof(arr) / sizeof(arr[0])); [&arr]() {for(auto i : arr) cout << i << endl;}(); }