文章目录
1.冒泡排序
1.1 非递归实现
- 核心代码:
void fun(int arr[],int n) {
for (int i = n - 1; i > 0; i--) {
for (int j = 0; j <= i; j++) {
if (arr[j] > arr[j+1]) {
int tmp = arr[j];
arr [j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
1.2 递归实现
- 核心代码:
void fun(int arr[],int n) {
if (n < 0) return ;
for (int j = 0; j < n; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
fun(arr, n - 1);
}
1.3 复杂度和稳定性
- 时间复杂度为O(N^2),空间复杂度为O(1)。
- 如果两个元素相等,是不会再交换的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
2.选择排序
2.1 非递归实现
- 核心代码:
void fun(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
int minlocation = i;
for(int j = i + 1; j < n; j++) {
if(arr[j] < arr[minlocation]) {
minlocation = j;
}
}
if(minlocation != i) {
swap(arr[i], arr[minlocation]);
}
}
}
2.2 递归实现
- 核心代码:
void fun(int arr[], int start, int n) {
if (start > =n) return ;
int minlocation=start;
for (int j = start + 1; j < n; j++) {
if (arr[j] < arr[minlocation]) {
minlocation = j;
}
}
if(minlocation != i) {
swap(arr[i], arr[minlocation]);
}
fun(arr, start + 1, n);
}
2.3 复杂度和稳定性
- 时间复杂度为O(N^2),空间复杂度为O(1)。
- 在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等 的元素后面,那么交换后稳定性就被破坏了。举个例子序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序是一个不稳定的排序算法。
3.插入排序
3.1 非递归实现
- 核心代码:
void fun(int arr[], int n) {
for (int i = 1; i < n; i++) {
int tmp = arr[i];
int j = i;
while (j - 1 >= 0 && arr[j - 1] > tmp) {
arr[j] = arr[j - 1];
j--;
}
arr[j] = tmp;
}
}
3.2 递归实现
- 核心代码:
void fun(int arr[], int start, int n) {
if (start >= n) return ;
int tmp = arr[start];
int j = start;
while (j - 1 >= 0 && arr[j - 1] > tmp){
arr[j] = arr[j - 1];
j--;
}
arr[j] = tmp;
fun(arr, start + 1, n);
}
3.3 复杂度和稳定性
- 时间复杂度为O(N^2),空间复杂度为O(1)。
- 比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比较。如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面,也就是说相等元素的前后顺序没有改变,所以插入排序是稳定的。
4.希尔排序
4.1 非递归实现
- 核心代码:
void fun1(int arr[], int start, int d, int n) {
for(int i = start + d; i < n; i = i + d) {
int tmp = arr[i];
int j = i;
while (j - d >= start && arr[j - d] > arr[j]) {
arr[j] = arr[j-d];
j = j - d;
}
arr[j] = tmp;
}
}
void fun(int arr[], int n) {
int d = n;
while (d / 2 >= 1) {
d = d / 2;
for (int i = 0; i < d; i++) {
fun1(arr, i, d, n);
}
}
}
4.2 复杂度和稳定性
- 时间复杂度小于O(N^2),空间复杂度为O(1)。
- 由于多次插入排序,虽然一次插入排序是稳定的,不会改变相同元素的相对顺序,但在多次插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以希尔排序是不稳定的排序算法。
5.快速排序
5.1 非递归实现
- 核心代码:
void fun(int arr[], int n) {
stack<pair<int, int>> st;
st.push({0, n - 1});
while (!st.empty()) {
pair<int, int> p = st.top();
st.pop();
int l = p.first, r = p.second;
int i = l, j = r;
int tmp = arr[l];
while (i < j) {
while (i < j && tmp <= arr[j]) j--;
arr[i] = arr[j];
while ( i< j && arr[i] <= tmp) i++;
arr[j] = arr[i];
}
arr[i] = tmp;
if(i + 1 < r ) {
st.push({i + 1, r});
}
if(l < i - 1) {
st.push({l, i - 1});
}
}
}
5.2 递归实现
- 核心代码:
void fun(int arr[], int l, int r) {
if (l >= r) return ;
int i = l, j = r;
int tmp = arr[l];
//int k = l + rand() % (r - l + 1);
//swap(arr[l], arr[k]);
while (i < j) {
while (i < j && tmp <= arr[j]) j--;
arr[i] = arr[j];
while (i < j && arr[i] < =tmp) i++;
arr[j] = arr[i];
}
arr[i] = tmp;
fun(arr, l, i - 1);
fun(arr, i + 1, r);
}
5.3 复杂度和稳定性
- 时间复杂度:O(nlogn);空间复杂度:快速排序使用递归,递归使用栈,因此它的空间复杂度为O(logn)。
- 快速排序无法保证相等的元素的相对位置不变,因此它是不稳定排序算法。
5.4 随机快速优化
- 基本的快速排序选取第一个或者最后一个元素作为主元。这样在数组已经有序的情况下,每次划分将得到最坏的结果。一种比较常见的优化方法是随机化算法,即随机选取一个元素作为主元。这种情况下虽然最坏情况仍然是O(n^2),但最坏情况不再依赖于输入数据,而是由于随机函数取值不佳。实际上,随机化快速排序得到理论最坏情况的可能性仅为
1/(2^n)
。所以随机化快速排序可以对于绝大多数输入数据达到O(nlogn)的期望时间复杂度。
6.归并排序
6.1 递归实现
- 核心代码:
void merge(int arr[], int l, int r) {
int temp[r - l + 1];
int mid = (l + r) / 2;
int i = l, j = mid + 1;
int k = 0;
while (i <= mid && j <= r) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while(i <= mid)
temp[k++] = arr[i++];
while(j <= r)
temp[k++] = arr[j++];
for(i = l, k = 0;i <= r; i++, k++){
arr[i] = temp[k];
}
}
void sort(int arr[], int l, int r) {
if(l < r){
int mid = (l + r) / 2;
sort(arr, l, mid);
sort(arr, mid + 1, r);
merge(arr, l, r);
}
}
6.2 复杂度和稳定性
- 每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。
- 在合并操作的时候,相等元素并不会改变相对位置,因此归并排序是稳定排序。
7.堆排序
7.1 非递归实现
- 核心代码:
void adjustHeap(int arr[], int start, int n) {
int tmp = arr[start];
for(int i = 2 * start + 1; i < n; i = 2 * i + 1) {
if (i + 1 < n && arr[i] < arr[i + 1]) {
i = i + 1;
}
if (arr[i] > tmp) {
arr[start] = arr[i];
start = i;
}else{
break;
}
}
arr[start] = tmp;
}
void sort(int arr[], int n) {
for(int i = n / 2 - 1; i >= 0; i--){
adjustHeap(arr, i, n);
}
for(int j = n - 1; j >= 0; j--) {
swap(arr[0], arr[j]);
adjustHeap(arr, 0, j);
}
}
7.2 复杂度和稳定性
- 时间复杂度O(nlogn),空间复杂度O(1)。
- 在堆调整操作的时候,相等元素不能保证不改变相对位置,因此堆排序是不稳定排序。
8.基数排序vs计数排序vs桶排序
- 这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
1)基数排序:根据键值的每位数字来分配桶;
2)计数排序:每个桶只存储单一键值;
3)桶排序:每个桶存储一定范围的数值;
9.算法总结
10.排序的相关应用
- 牛客网之塔:此题目属于排序的应用。
- 牛客网之鸡鸭分类问题:这个用到排序思想,我们可以看作排序问题来解决。
- 牛客网之美妙的约会:此题目属于排序的应用。
- LeetCode之逆序对