【数据结构】常见的排序算法 -- 交换排序

🫧个人主页: 小年糕是糕手
💫个人专栏:《数据结构(初阶)》《C/C++刷题集》《C语言》
🎨你不能左右天气,但你可以改变心情;你不能改变过去,但你可以决定未来!

目录
一、冒泡排序
1.1、算法思想
1.2、代码实现
二、快速排序
2.1、快速排序实现主框架
2.2、找基准值的三种方法
2.2.1、hoare版本
2.2.1.1、算法思路
2.2.1.2、代码实现
2.2.2、挖坑法
2.2.2.1、算法思路
2.2.2.2、代码实现
2.2.3、lomuto前后指针
2.2.3.1、算法思路
2.2.3.2、代码实现
2.3、非递归版本
交换排序基本思想:
所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置
交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动
一、冒泡排序
1.1、算法思想
前面在C语言博客专栏中我们已经接触过冒泡排序的思路了,冒泡排序是一种最基础的交换排序。之所以叫做冒泡排序,因为每一个元素都可以像小气泡一样,根据自身大小一点一点向数组的一侧移动。
- 从序列的第一个元素开始,依次比较相邻的两个元素。
- 若前一个元素大于后一个元素,则交换它们的位置。
- 继续向后比较,直到完成对序列中最后一个未排序元素的比较,此时本轮最大的元素会 “冒泡” 到序列末尾(即已排序部分的起始位置)。
- 忽略已排好序的末尾元素,对剩余未排序的元素重复上述过程。
- 当某一轮遍历中没有发生任何元素交换时,说明序列已完全有序,排序结束。

1.2、代码实现
//1)冒泡排序
void BubbleSort(int* arr, int n)
{int exchange = 0;for (int i = 0; i < n; i++){for (int j = 0; j < n - i - 1; j++){if (arr[j] > arr[j + 1]){exchange = 1;Swap(&arr[j], &arr[j + 1]);}}if (exchange == 0){break;}}
}
冒泡排序的特性总结
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
二、快速排序
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
2.1、快速排序实现主框架
//2)快速排序
void QuickSort(int* arr, int left, int right)
{if (left >= right){return;}//找基准值keyiint keyi = _QuickSort(arr, left, right);//左序列[left,keyi - 1] 右序列[keyi + 1,right]QuickSort(arr, left, keyi - 1);QuickSort(arr, keyi + 1, right);
}
将区间中的元素进行划分的 _QuickSort 方法主要有以下几种实现方式:
2.2、找基准值的三种方法
2.2.1、hoare版本
2.2.1.1、算法思路
算法思路:
1)创建左右指针,确定基准值
2)从右向左找出比基准值小的数据,从左向右找出比基准值大的数据,左右指针数据交换,进入下次循环
2.2.1.2、代码实现
//找基准值 -- hoare版本
int _QuickSort1(int* arr, int left, int right)
{int keyi = left;left++;while (left <= right){//right:从右往左找比基准值要小的while (left <= right && arr[right] > arr[keyi]){--right;}//left:从左往右找比基准值要大的while (left <= right && arr[left] < arr[keyi]){++left;}//left和right交换if (left <= right){Swap(&arr[left], &arr[right]);left++;right--;}}//right的位置就是基准值的位置Swap(&arr[keyi], &arr[right]);//交换值return right;
}
2.2.2、挖坑法
2.2.2.1、算法思路
思路:
创建左右指针。首先从右向左找出比基准小的数据,找到后立即放入左边坑中,当前位置变为新的"坑",然后从左向右找出比基准大的数据,找到后立即放入右边坑中,当前位置变为新的"坑",结束循环后将最开始存储的分界值放入当前的"坑"中,返回当前"坑"下标(即分界值下标)

2.2.2.2、代码实现
int _QuickSort2(int* arr, int left, int right)
{int hole = left;int key = arr[hole];while (left < right){while (left < right && arr[right] > key){--right;}arr[hole] = arr[right];hole = right;while (left < right && arr[left] < key){++left;}arr[hole] = arr[left];hole = left;}arr[hole] = key;return hole;
}
2.2.3、lomuto前后指针
2.2.3.1、算法思路
创建前后指针,从左往右找比基准值小的进行交换,使得小的都排在基准值的左边。
cur从左往右找比基准值要小的数据
1)cur指向的数据比基准值要小,++prev,cur和prev交换值,++cur
2)cur指向的数据不比基准值要小,++cur

2.2.3.2、代码实现
//找基准值 -- lomuto前后指针
int _QuickSort3(int* arr, int left, int right)
{int prev = left, cur = left + 1;int keyi = left;while (cur <= right){//cur数据和基准值比较if (arr[cur] < arr[keyi] && ++prev != cur){Swap(&arr[cur], &arr[prev]);}++cur;}Swap(&arr[keyi], &arr[prev]);return prev;}
快速排序特性总结:
- 时间复杂度:O(n*logn)
- 空间复杂度:O(logn)
2.3、非递归版本
非递归版本的快速排序需要借助数据结构:栈
//非递归版本快速排序
// 交换两个元素
void swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}// 分区操作:返回基准值最终位置
int partition(int arr[], int left, int right) {// 选择最右侧元素作为基准值int pivot = arr[right];// i 指向小于基准值区域的最后一个元素int i = left - 1;for (int j = left; j < right; j++) {// 若当前元素小于等于基准值,加入左侧区域if (arr[j] <= pivot) {i++;swap(&arr[i], &arr[j]);}}// 将基准值放到正确位置(i+1 是基准值的最终索引)swap(&arr[i + 1], &arr[right]);return i + 1;
}// 非递归快速排序
void quickSort(int arr[], int n) {if (n <= 1) return;// 用数组模拟栈,存储区间的左右边界(每个区间占两个位置)int* stack = (int*)malloc(2 * n * sizeof(int));int top = -1; // 栈顶指针// 初始区间 [0, n-1] 入栈stack[++top] = 0;stack[++top] = n - 1;// 栈不为空时循环处理while (top >= 0) {// 弹出右边界和左边界(注意栈的先入后出特性)int right = stack[top--];int left = stack[top--];// 分区操作,获取基准值位置int pivotIndex = partition(arr, left, right);// 左子区间 [left, pivotIndex-1] 入栈(若有效)if (pivotIndex - 1 > left) {stack[++top] = left;stack[++top] = pivotIndex - 1;}// 右子区间 [pivotIndex+1, right] 入栈(若有效)if (pivotIndex + 1 < right) {stack[++top] = pivotIndex + 1;stack[++top] = right;}}free(stack); // 释放栈内存
}