直接插入排序
算法思想
首先将第二个数与第一个数进行对比,如果第二个数比第一个数小,则将第二个数插入到第一个数之前,这样保证前两个数是有序的;
接下来将第三个数与前两个数对比,发现有比第三个数大的数即将第三个数插入到对应数的前面,这样一次插入可保证前三个数是有序的;
以此类推,将后面的i个数分别其前面的i-1个数进行对比,并将其插入到第一个比其大的数前面,最后即可完成排序。
时间空间复杂度及稳定性
- T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2)
- S ( n ) = O ( 1 ) S(n)=O(1) S(n)=O(1)
- 稳定
代码
void InsertSort(vector<int> v) {
for (int i = 1; i < v.size(); i) {
for (int j = 0; j < i; j) {
if (v[j] > v[i]) {
int t = v[i];
for (int k = i; k > j; --k) {
v[k] = v[k - 1];
}
v[j] = t;
}
}
}
}
希尔排序
算法思想
希尔排序又称缩小增量排序,也属于一种插入排序算法,其基本思想是将要被排序的数列分成若干个子序列然后分别进行插入排序,待这些子序列排序完成后大序列就会基本有序,这时再对整个大序列进行一次直接插入排序;
其分散数列的原则一般是5-3-1,即先将大数列每隔5个取一个数,这样最终会形成5个子数列,将这5个子数列分别进行插入排序,然后将排序好的数列每隔3个取一个形成3个子数列,再对这三个子数列进行三次插入排序,最后,对整个基本有序的大数列进行一次插入排序达到排序整个数列的目的。
时间空间复杂度即稳定性
- T ( n ) = O ( n 1.5 ) T(n)=O(n^{1.5}) T(n)=O(n1.5)
- S ( n ) = O ( 1 ) S(n)=O(1) S(n)=O(1)
- 不稳定
代码
void ShellSort(vector<int> v) {
int d[3] = {5, 3, 1};
for (int i = 0; i < 3; i) {
ShellInsert(v, d[i]);
}
show(v);
}
void ShellInsert(vector<int> &v, int d) {
for (int l = 0; l < d; l) {
for (int i = l 1; i < v.size(); i = d) {
for (int j = l; j < i; j = d) {
if (v[i] < v[j]) {
int t=v[i];
for(int k=i;k>j;i-=d){
v[k]=v[k-d];
}
v[j]=t;
}
}
}
}
}
冒泡排序
算法思想
冒泡排序的主要思想是将大的数向下“沉”,将小的数向上“气泡”,具体过程:
首先将第一个数与第二个数进行对比,若第二个数小于第一个数,则将第一个数与第二个数交换,然后比较第二个数和第三个数,如果第二个数大于第三个数,则将其对换,最后的结果是将数列中最大的一个数换到数列的最后一位。
然后再对前n-1个数进行相同的过程,结果是将倒数第二大的数放在倒数第二位。
以此类推,经历n-1次后所有数列将会有序。
时间空间复杂度及稳定性
- T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2)
- S ( n ) = O ( 1 ) S(n)=O(1) S(n)=O(1)
- 稳定
代码
void BubbleSort(vector<int> v) {
for (int i = 0; i < v.size(); i) {
int flag = 1;
for (int j = 0; j < v.size() - i && j < v.size() - 1; j) {
if (v[j] > v[j 1]) {
int t;
t = v[j];
v[j] = v[j 1];
v[j 1] = t;
flag = 0;
}
}
if (flag == 1) {//说明已经有序了
break;
}
show(v);
}
快速排序
算法思想
快速排序是对冒泡排序的一种改进,其基本思路是通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
时间空间复杂度及稳定性
- T ( n ) = O ( n l o g 2 n ) T(n)=O(nlog_2n) T(n)=O(nlog2n)
- S ( n ) = O ( l o g 2 n ) S(n)=O(log_2n) S(n)=O(log2n)
- 不稳定
代码
void QuickSort(vector<int> v) {
QSort(v, 0, v.size() - 1);
show(v);
}
void QSort(vector<int> &v, int low, int high) {
if (low >= high) {
return;
}
int t = Partition(v, low, high);
QSort(v, low, t - 1);
QSort(v, t 1, high);
}
int Partition(vector<int> &v, int low, int high) {
int pivotkey;
pivotkey = v[low];
while (low < high) {
while (low < high && v[high] >= pivotkey) {
--high;
}
int t;
t = v[low];
v[low] = v[high];
v[high] = t;
while (low < high && v[low] <= pivotkey) {
low;
}
t = v[low];
v[low] = v[high];
v[high] = t;
}
return low;
}
选择排序
算法思想
选择排序的思路是首先找到序列中的最小数,将其放在第一位,然后找到第二小的数将其放在第二位,以此类推,最终将所有第i小的数放在第i位从而达到排序目的。
时间空间复杂度及稳定性
- S ( n ) = O ( 1 ) S(n)=O(1) S(n)=O(1)
- T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2)
- 不稳定
代码
void SelectSort(vector<int> v) {
for (int i = 0; i < v.size(); i) {
int min;
min = i;
for (int j = i 1; j < v.size(); j) {
if (v[j] < v[min]) {
min = j;
}
}
int t;
t = v[min];
v[min] = v[i];
v[i] = t;
}
show(v);
}
堆排序
算法思想
堆排序的主要思路是先将所要排序的数列看做是一颗完全二叉树并建立大顶堆,然后将堆顶元素放在堆的最后一位,然后再调整该二叉树为大顶堆,然后再将堆顶放在二叉树的最后一位,调整二叉树为大顶堆,重复这一过程直至完成排序。
时间空间复杂度及稳定性
- T ( n ) = O ( n l o g 2 n ) T(n)=O(nlog_2n) T(n)=O(nlog2n)
- S ( n ) = O ( 1 ) S(n)=O(1) S(n)=O(1)
- 不稳定
代码
void HeapSort(vector<int> v) {
int size = v.size();
v.push_back(0);
for (int k = v.size(); k > 0; --k) {
v[k] = v[k - 1];
}
//建堆
for (int i = size / 2; i > 0; --i) {
HeapAdjust(v, i, size);
}
for (int j = size; j > 1; --j) {
int t;
t = v[1];
v[1] = v[j];
v[j] = t;
HeapAdjust(v, 1, j - 1);
}
for (int i = 1; i < v.size(); i) {
cout << v[i] << " ";
}
cout << endl;
}
void HeapAdjust(vector<int> &v, int s, int m) {
/*
* 已知v[s..m]除v[s]之外均满足堆的定义,本函数调整v[s],使得v[s..m]成为一个小顶堆
*/
int rc;
rc = v[s];
for (int i = 2 * s; i <= m; i = 2 * i) {
if (i < m && v[i] < v[i 1]) {
i ;//i为较大数据的下标
}
if (rc >= v[i]) {
break;
}
v[s] = v[i];
s = i;
}
v[s] = rc;
}
归并排序
算法思想
归并排序的主要思路是将索要排序数列看做若干个有序的小数列,因为将两个有序数列合并之后所得数列还是有序数列,所以经过不断合并,最后可将数列排为有序。
时间空间复杂度及稳定性
- T ( n ) = O ( n l o g 2 n ) T(n)=O(nlog_2n) T(n)=O(nlog2n)
- S ( n ) = O ( n ) S(n)=O(n) S(n)=O(n)
- 稳定
代码
void MSort(vector<int> v) {
vector<int> h;
h = v;
int start, seg;
for (seg = 1; seg < v.size(); seg *= 2) {
int k = 0;
for (start = 0; start < v.size(); start = start seg * 2) {
int end;
end = start seg;
int low = start;
while (low < start seg && end < start seg seg && low < v.size() && end < v.size()) {
if (v[low] <= v[end]) {
h[k ] = v[low];
low ;
} else {
h[k ] = v[end];
end ;
}
}
while (low < start seg && low < v.size()) {
h[k ] = v[low ];
}
while (end < start seg seg && end < v.size()) {
h[k ] = v[end ];
}
}
v = h;
}
show(v);
}
基数排序
算法思想
基数排序需要经历d次,d为所要排序数列中位数最多的数的位数,其过程是首先根据数列中数的个位的数值将所有数入09这10个队列,然后从09将元素依次出队,然后再根据十位元素的数值再次入队,然后出队,以此类推重复d次,最终即可完成排序。
时间空间复杂度及稳定性
- T ( n ) = O ( d ∗ n ) T(n)=O(d*n) T(n)=O(d∗n) d为排序数中最大数的位数
- S ( n ) = O ( n ) S(n)=O(n) S(n)=O(n)
- 稳定
代码
void radixSort(vector<int> v) {
int d = GetMaxBit(v);
int *count = new int[10];
queue<int> q[10];
int radix = 1;
for (int i = 0; i < d; i) {
for (int j = 0; j < v.size(); j) {
int t;
t = (v[j] / radix) % 10;
q[t].push(v[j]);
}
int p = 0;
for (int k = 0; k < 10; k) {
while (!q[k].empty()) {
v[p ] = q[k].front();
q[k].pop();
}
}
radix *= 10;
}
show(v);
}
复杂度总结
排序方法 | 时间复杂度 | 空间复杂度 |
---|---|---|
直接插入排序 | T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2) | S ( n ) = O ( 1 ) S(n)=O(1) S(n)=O(1) |
希尔排序 | T ( n ) = O ( n 1.5 ) T(n)=O(n^{1.5}) T(n)=O(n1.5) | S ( n ) = O ( 1 ) S(n)=O(1) S(n)=O(1) |
冒泡排序 | T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2) | S ( n ) = O ( 1 ) S(n)=O(1) S(n)=O(1) |
快速排序 | T ( n ) = O ( n l o g 2 n ) T(n)=O(nlog_2n) T(n)=O(nlog2n) | S ( n ) = O ( l o g 2 n ) S(n)=O(log_2n) S(n)=O(log2n) |
选择排序 | S ( n ) = O ( 1 ) S(n)=O(1) S(n)=O(1) | T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2) |
堆排序 | T ( n ) = O ( n l o g 2 n ) T(n)=O(nlog_2n) T(n)=O(nlog2n) | S ( n ) = O ( 1 ) S(n)=O(1) S(n)=O(1) |
归并排序 | T ( n ) = O ( n l o g 2 n ) T(n)=O(nlog_2n) T(n)=O(nlog2n) | S ( n ) = O ( n ) S(n)=O(n) S(n)=O(n) |
基数排序 | T ( n ) = O ( d ∗ n ) T(n)=O(d*n) T(n)=O(d∗n) d为排序数中最大数的位数 | S ( n ) = O ( n ) S(n)=O(n) S(n)=O(n) |