名字 | 最好O(n) | 平均O(n) | 最坏O(n) | 时间复杂度是否与初始顺序有关 | 是否稳定 | 空间复杂度 | 算法描述 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | Yes | No | O(1) | 每一趟从待排序的记录中选出关键字最小的记录,顺序放在已排好序的子文件的最后,直到全部记录排序完毕。常用的选择排序方法有直接选择排序和堆排序 |
直接插入 | O(n) | O(n^2) | O(n^2) | Yes | Yes | O(1) | 每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子文件中的适当位置,直到全部记录插入完成为止。 |
冒泡排序 | O(n) | O(n^2) | O(n^2) | Yes | O(1) | 两两比较待排序记录的关键字,发现两个记录的次序相反时即进行交换,直到没有反序 的记录为止。应用交换排序基本思想的主要排序方法有:冒泡排序和快速排序。 | |
希尔排序 | O(n) | O(nlogn) | O(n^s)(1<s<2) | No | No | O(1) | 插入排序。相同距离的元素做插入排序。 |
快速排序 | O(nlogn) | O(nlogn) |
O(n^2)
每次划分都是最大程度不对称:(n-1)+0
| Yes | No | O(logn) | 交换排序。分治法。在数组中任意选一个记录作为基准(pivot),以此基准将当前无序区划分为左右两个较小的子区间,使左区间的所有数都小于等于pivot,右边区间的数都大于等于pivot,而pivot则位于正确的位置上,不用参加后续的排序。 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | No | O(1) | 选择排序。 堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,方便选取最大(最小)元素。 将 R[1..n]构造为初始堆; - 每一趟排序的基本操作:将当前 无序区的堆顶记录 R[1] 和该区间的最后一个记录交换,然后将新的无序区调整为堆(亦称重建堆)。 | |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | Yes | O(n) | 归并是指将若干个已排序的子文件合并成一个有序的文件。分治法,自顶向下。 |
当文件为正序时,直接插入和冒泡均最佳。
在待排序的文件中,若存在多个关键字相同的记录,经过排序后这些具有相同关键字的记录之间的相对次序保持 不变,该排序方法是稳定的;若具有相同关键字的记录之间的相对次序发生变化,则称这种排序方法是不稳定 的。
直接选择排序:
第 i 趟排序:第i趟排序开始时,当前有序区和无序区分别为 R[1..i-1]和 R[i..n][1≤i≤n-1]。该趟排序从当前 无序区中选出关键字最小的记录 R[k],将它与无序区的第 i 个记录 R[i]交换,使 R[1..i]和 R[i+1..n]分别变为 记录个数增加 1 个的新有序区和记录个数减少 1 个的新无序区。
直接插入排序:
每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子文件中的适当位置,直到全部记录插入完成为止。
直接插入排序的基本操作是将当前无序区的第 1 个记录 R[i]插人到有序区 R[1..i-1]中适当的位置上,使 R[1..i]变为新的有序区。
像玩扑克牌一样,从无序的牌堆顶部取走一张牌,使之放在左手有序牌的正确位置上,就需要从左向右或者从右向左遍历。
冒泡排序:
两两比较待排序记录的关键字,发现两个记录的次序相反时即进行交换,直到没有反序 的记录为止。应用交换排序基本思想的主要排序方法有:冒泡排序和快速排序。
因为每一趟排序都使有序区增加了一个气泡,在经过 n-1 趟排序之后,有序区中就有 n-1 个气泡,而无序区中气 泡的重量总是大于等于有序区中气泡的重量,所以
整个冒泡排序过程至多需要进行 n-1 趟排序。
flag标记表示是否存在逆序。
每趟扫描仅能使最重气泡"下沉"一个位置,因此使位于顶端的最重气泡下沉到底部时,需做 n-1 趟扫描。
快速排序:
方法1:
方法2:
过程:分解-》求解-》组合
划分的步骤:
1.两个指针i,j分别指向数组的上界和下界i=left,j=right,选取第一个元素a[left],将值保存在pivot中。
2.j从right处自右向左扫描,直到找到第一个小于pivot的元素a[j],然后将a[j]与a[i]交换,使关键字小宇pivot的移到了基准的左边,交换后a[j]的值就是pivot;然后令i从i+1的位置开始从左向右扫描,直到找到第一个大于pivot的元素a[i],交换a[i]与a[j],使关键字大于pivot的元素放到了pivot的右边,此时a[i]内的元素就是pivot;然后令j从j-1处从右向左扫描,如此交替改变扫描方向,直到i=j时,i便是pivot的最终位置,将pivot的放到此位置就完成了一次划分。
求解:
根据划分,以pivot的位置把数组分成两个部分,依次调用快排
组合:
将上面两部分的函数返回结果直接连接起来就得到了最终结果。
希尔排序:
先取一个小于 n 的整数 d1 作为第一个增量,把文件的全部记录分成 d1 个组。所有距离为 d1 的倍数的记录放在同一个组中。先在
各组内进行直接插人排序;然后,取第二个增量 d2<d1重复上述的分组和排序,直到所取的增量dt=1(dt<dt-1<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
好的增量序列的共同特征:
• 最后一个增量必须为 1; d/3+1
• 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。
堆排序:
n 个关键字序列 K1,K2,…,Kn 称为堆,当且仅当该序列满足如下性质(简称为堆性质):
若将此序列所存储的向量 R[1..n]看做是一棵
完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉 树:树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。
根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆。 根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆。
堆排序与直接选择排序的比较:
在直接选择排序中,R[1..n]中选出最小元素需要做n-1次比较,然后在 R[2..n]中选出关键字最小的记录,又需要做 n-2 次比较。事实上,后面的 n-2 次比较中,有许多比较可能在前面的 n-1 次比较中已经做过,有一些重复比较,堆排序可以根据树形结构保存部分比较结果,减少比较次数。
大根堆排序的基本思想:
1.先将初始数组构成一个大根堆,此堆为初始的无序区(层序遍历无序)。
2.将堆顶最大的元素与无序区的最后一个记录R[n]对换,得到新的无序区R[1..n-1]和有序区R[n]。满足无序区的所有元素小于R[n]。
3.重新调整无序区元素使之构成大根堆,再将R[1,...,n-1]中最大的元素R[1]与R[n-1]对调。得到新的无序区R[1..n-2]和有序区R[n-1,...,n-1].同样将R[1,...,n-2]调整成大根堆,知道无序区只有一个元素为止。
完全二叉树的基本性质:
数组中有n个元素,i是节点,1 <= i <= n/2 就是说数组的后一半元素都是叶子节点。
i的父节点位置:i/2
i左子节点位置:i*2
i右子节点位置:i*2 + 1
在重建堆的过程中,只用调整比父节点大的子节点所在的那部分子树。
由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件
归并排序:
设归并排序的当前区间是R[low..high],分治法的三个步骤是:
• 分解:将当前区间一分为二,即求分裂点
• 求解:递归地对两个子区间 R[low..mid]和 R[mid+1..high]进行归并排序;
• 组合:将已排序的两个子区间 R[low..mid]和 R[mid+1..high]归并为一个有序的区间 R[low..high]。
递归的终结条件:子区间长度为 1(一个记录自然有序)。
桶排序:
桶排序的平均时间复杂度是线性的,即 O(n)。但最坏情况仍有可能是 O(n2)。
箱排序只适用于关键字取值范围较小的情况,否则所需箱子的数目 m 太多导致浪费存储空间和计算时间。
伪代码:
基数排序:
基数排序的基本思想是:从低位到高位依次对 Kj(j=d-1,d-2,…,0)进行箱排序。在 d 趟箱排序中,所需的箱 子数就是基数 rd,这就是"基数排序"名称的由来。
箱排序也称桶排序(Bucket Sort),其基本思想是:设置若干个箱子,依次扫描待排序的记录 R[0],R[1],…,R[n-1],把关键字等于 k 的记录全都装入到第 k 个箱子里(分配),然后按序号依次将各非空的 箱子首尾连接起来(收集)。
基数排序的时间是线性的(即 O(n))。 基数排序所需的辅助存储空间为 O(n+rd)。 基数排序是稳定的。
1.首先计算出数组中最大的数的数的位数
2.桶的个数设为10,即从0到9
3.然后从数的个位开始,数组里面所有数根据个位的数字,决定该数映射到那个桶中,统计每个桶中元素的个数count[i]。
4.让每个桶的计数变为从第1个桶到该桶之间所有元素的个数。 for i i to 9: count[i]=count[i-1]+count[i]
5.把桶中最优元素安装桶的顺序连接起来
6.然后从十位开始重复3开始的操作,直到最大位数都处理完
各类排序算法实现:
- void quicksort(vector<int>&array, int left, int right)
- {
- if (left >= right)
- return;
- int pivot = array[left];
- int i = left, j = right;
- while (i < j)
- {
- while (i<j&&array[j] >= pivot)
- j--;
- if(i<j)
- array[i++] = array[j];
- while (i<j&&array[i] <= pivot)
- i++;
- if (i<j)
- array[j--] = array[i];
- }
- array[i] = pivot;
- quicksort(array, left, i - 1);
- quicksort(array, i + 1, right);
- }
- void quicksort1(vector<int>&array, int left, int right)
- {
- if (left >= right)
- return;
- int m = left;
- for (int i = left + 1; i <= right; i++)
- {
- if (array[i] < array[left])
- swap(array[++m], array[i]);
- }
- swap(array[left], array[m]);
- quicksort1(array, left, m - 1);
- quicksort1(array, m + 1, right);
- }
- void insertsort(vector<int>&a)
- {
- int n = a.size();
- int j;
- for (int i = 1; i < n; i++)
- {
- if (a[i] < a[i - 1])
- {
- j = i - 1;
- int pos = a[i];
- for (; j >= 0; j--)
- {
- if (pos < a[j])
- {
- a[j + 1] = a[j];
- }
- else
- break;
- }
- a[j + 1] = pos;
- }
- }
- }
- void choosesort(vector<int>&a)
- {
- int n = a.size();
- int k;
- for (int i = 0; i < n-1; i++)
- {
- k = i;
- for (int j = i; j < n; j++)
- {
- if (a[j] < a[k])
- {
- k = j;
- }
- }
- if (k != i)
- swap(a[k], a[i]);
- }
- }
- void bubblesort(vector<int>&a)
- {
- int n = a.size();
- for (int i = 1; i < n; i++)
- {
- bool flag = false;
- for (int j = n - 1; j >= i; j--)
- {
- if (a[j] < a[j - 1])
- {
- swap(a[j], a[j - 1]);
- flag = true;
- }
- }
- if (flag == false)
- break;
- }
- }
- void shellPass(vector<int>&a,int d)
- {
- int n = a.size();
- int j;
- for (int i = d; i < n; i++)
- {
- if (a[i] < a[i - d])
- {
- int pos = a[i];
- for (j = i - d; j >= 0; j -= d)
- {
- if (pos < a[j])
- a[j + d] = a[j];
- else
- break;
- }
- a[j + d] = pos;
- }
- }
- }
- void shellsort(vector<int>&a)
- {
- int d = a.size();
- do{
- d = d / 3 + 1;
- shellPass(a, d);
- } while (d > 1);
- }
- void max_heap(vector<int>&a, int p,int len)
- {
- int cur_parent = a[p];
- int child = 2 * p + 1;
- while (child < len)
- {
- if (child+1<len&&a[child] < a[child + 1])
- child++;
- if (cur_parent < a[child])
- {
- a[p] = a[child];
- p = child;
- child = 2 * p + 1;
- }
- else
- break;
- }
- a[p] = cur_parent;
- }
- void heapsort(vector<int>&a)
- {
- int n = a.size();
- for (int i = n / 2; i >= 0; i--)
- max_heap(a, i, n);
- for (int i = n-1; i >= 1; i--)
- {
- swap(a[0], a[i]);
- max_heap(a, 0, i);
- }
- }
- vector<int> TwoWaysMerge(vector<int>&a, vector<int>&b)
- {
- vector<int>c(a.size() + b.size());
- int i = 0;
- int j = 0;
- for (int k = 0; k < a.size() + b.size(); k++)
- {
- if (i < a.size() && j < b.size())
- {
- if (a[i] < b[j])
- c[k] = a[i++];
- else
- c[k] = b[j++];
- }
- else if (i < a.size()&&j>=b.size())
- c[k] = a[i++];
- else if (i >= a.size() && j < b.size())
- c[k] = a[j++];
- }
- return c;
- }
- void merge(vector<int>&a, int low, int m, int high)
- {
- vector<int>c(high-low+1);
- int i = low;
- int j = m + 1;
- int k = 0;
- while (i <= m&&j <= high)
- c[k++] = (a[i] <= a[j]) ? a[i++] : a[j++];
- while (i <= m)
- c[k++] = a[i++];
- while (j <= high)
- c[k++] = a[j++];
- for (i = low,j=0; i <=high; i++,j++)
- a[i] = c[j];
- }
- void mergesort(vector<int>&a,int left,int right)
- {
- int n = a.size();
- if (right-left <= 0)
- return;
- int mid = left+(right-left) / 2;
- mergesort(a, left, mid);
- mergesort(a, mid + 1, right);
- merge(a, left, mid, right);
- }
- int max_count(vector<int>&a)
- {
- int maxn = a[0];
- for (int i = 1; i < a.size(); i++)
- {
- if (a[i]>maxn)
- maxn = a[i];
- }
- int count = 0;
- while (maxn)
- {
- maxn /= 10;
- count++;
- }
- return count;
- }
-
- //基数排序
- void redixsort(vector<int>&a)
- {
- int d = max_count(a);
- vector<int>tmp(a.size());
- vector<int>count(10,0);
- int redix = 1;
- for (int i = 0; i < d; i++)
- {
- for (int j = 0; j < 10; j++)
- count[j] = 0;
- for (int j = 0; j < a.size(); j++)
- {
- int k = (a[j] / redix) % 10;
- count[k]++;
- }
- for (int j = 1; j < 10; j++)
- count[j] = count[j - 1] + count[j];
- for (int j = a.size()-1; j >=0; j--)
- {
- int k = (a[j] / redix) % 10;
- tmp[count[k] - 1] = a[j];
- count[k]--;
- }
- a = tmp;
- redix *= 10;
- }
- }