文章目录
- 排序算法
- 计数排序
- 选择排序
- 冒泡排序
- 插入排序
排序算法
排序算法是解决问题中常见且非常重要的一环,针对相应的问题选择相应的排序算法,能够提高问题解决速度,节省时间!!!
常见的排序算法有:
排序算法 | 关键步骤 | 时间复杂性 | |
---|---|---|---|
最好 | 最坏 | ||
计数排序 | 比较 | n(n - 1)/2 + n | n(n - 1)/2 + 2n - 1 |
移动 | 0 | 6(n - 1) | |
选择排序(及时终止的) | 比较 | n - 1 | n(n - 1)/2 |
移动 | 3 | 3(n - 1) | |
冒泡排序(及时终止的) | 比较 | n - 1 | n(n - 1)/2 |
移动 | 0 | 3n(n - 1)/2 | |
插入排序 | 比较 | n - 1 | n(n - 1)/2 |
移动 | 2(n - 1) | 2(n - 1) + n(n - 1)/2 |
下面就这些排序算法进行实现及分析。
计数排序
- 主要思路及实现
- 步骤一:先对数组中的元素计算相应的名次,并将相应的名次保存在 rank 数组中;
- 如何计算名次?
每一个元素 a[k]a[k]a[k] 都和它前面的 k−1k - 1k−1 个元素进行比较,若 a[k]>a[i](i∈[0,k−2])a[k] > a[i](i \in [0, k - 2])a[k]>a[i](i∈[0,k−2]),则 rank[k]++;反之则 rank[i]++;for (int i = 1; i < n; ++i){for (int j = 0; j < i; ++j){if (a[i] < a[j])++rank[j];else++rank[i];}}
- 如何计算名次?
- 步骤二:根据对应的名字对原数组中的元素进行原地重排;
- 怎样进行重排?
当 rank[i]!=irank[i] != irank[i]!=i 时,使用 swap() 函数对 a[i]a[i]a[i] 和 a[rank[i]]a[rank[i]]a[rank[i]] 进行交换即可;注意交换不只是 a 数组要进行交换,rank 数组也要跟着一起!!!for (int i = 0; i < n; ++i){while (rank[i] != i){swap(a[i], a[rank[i]]);swap(rank[i], rank[rank[i]]);}}
- 怎样进行重排?
//计数排序 template<typename T> void countSort(T* a, int n) {//计算名次int* rank = new int[n] ();for (int i = 1; i < n; ++i){for (int j = 0; j < i; ++j){if (a[i] < a[j])++rank[j];else++rank[i];}}//按照名次进行原地重排for (int i = 0; i < n; ++i){while (rank[i] != i) //注意 swap 中 a 和 rank 都得换哦{swap(a[i], a[rank[i]]);swap(rank[i], rank[rank[i]]);}}delete[] rank; }
- 步骤一:先对数组中的元素计算相应的名次,并将相应的名次保存在 rank 数组中;
- 时间复杂性分析
-
比较次数
-
在计算名次的时候,每一个元素都要和它前面的元素进行比较,因此此时经历了compare1=n(n−1)2次比较compare_1 = \frac{n(n - 1)}{2} \text{次比较}compare1=2n(n−1)次比较
-
在原地重排时,我们需要考虑最好和最坏两种情况(不考虑循环变量的比较)。
最好情况下:
所有元素开始均已有序,因此 r[i] != i 只需要执行 n 次即可;最坏情况下:
每一次交换都会是一个元素到达正确的位置,假设第 1 个位置在不停地进行交换,那么进行 n - 1 次后,所有的元素都会到达其正确的位置。此后只需进行 n 次 r[i] != i 判断即可。因此共 2n - 1 次;
-
-
交换次数与移动次数
在按照名次进行原地重排时,
最好情况下:
一次交换也不做,因此此时交换次数为 0;最坏情况下:
每次都要作交换,并且 n 个元素进行 n - 1 次交换之后,第 n 个元素必有序,此时只需 n - 1 次 swap 即可;因此,交换次数:0−2(n−1)0 - 2(n - 1)0−2(n−1),由于每次交换涉及 3 次移动,所以移动次数:0−3∗2(n−1)0 - 3 * 2(n - 1)0−3∗2(n−1)。
-
选择排序
- “最原始的” 选择排序
-
主要思路及实现
- 步骤一:在有 n−i(i∈0,1,2,⋯,n−1)n - i(i \in {0, 1, 2, \cdots, n - 1})n−i(i∈0,1,2,⋯,n−1) 个元素的数据集中找出最大元素;
- 步骤二:将这个最大元素与数据集中最后一个元素进行交换;
- 重复步骤一、二,直到得到只有 1 个元素的数据集;
具体实现:
//寻找数据集中的最大值对应的索引 template<typename T> int indexOfMax(T* a, int n) {if (n < 1)return -1;int indexOfMax = 0;for (int i = 0; i < n; ++i)if (a[indexOfMax] < a[i])indexOfMax = i;return indexOfMax; }//选择排序 template<typename T> void selectSort(T* a, int n) {//找到最大元素,然后与最后一个元素进行交换for (int i = n; i > 0; --i)swap(a[i - 1], a[indexOfMax(a, i)]); }
-
时间复杂性分析:
- 比较次数:在对最大值对应的索引进行求取时,每个 iii 都要在 indexOfMax() 函数中比较 i - 1 次;因此:compare1=1+2+⋯+n−1=n(n−1)2次compare_1 = 1 + 2 + \cdots + n - 1 = \frac{n(n - 1)}{2} \text{次}compare1=1+2+⋯+n−1=2n(n−1)次
- 交换次数与移动次数
同理,进行 n - 1 次交换之后,第 n 个元素必在该在的位置上,因此,交换次数:n−1n - 1n−1,移动次数:3(n−1)3(n - 1)3(n−1)。
-
- “及时终止” 的选择排序
有时候不一定要一直进行排序到只剩下一个元素,当某一次函数执行时数据已经有序了,那么此时便可以不进行下面的过程了。-
主要思路及实现
- 步骤一:设置 bool 值 sorted 来表示序列是否已经有序,并赋初始值为 false;
- 步骤二:在 for 循环中寻找最大索引时,我们假设序列已经有序,sorted = true;若 if 中条件一直满足,说明假设成立,最终 sorted = true,不再进行下一次循环;
若序列仍无序,则必会进入 else 语句,sorted = false。从而达到了及时退出的目的。
具体实现:
//及时终止的选择排序 template<typename T> void improved_selectSort(T* a, int n) {bool sorted = false;for (int i = n; !sorted && i > 0; --i){int indexOfMax = 0;//假设已经有序了sorted = true;//找最大索引for (int j = 0; j < i; ++j){ //如果是有序的,就会一直走 if,从而 sorted = true,下次不再进入循环;只要有一个不满足,sorted 就会置为false,说明无序if (a[indexOfMax] < a[j])indexOfMax = j;elsesorted = false;}swap(a[i - 1], a[indexOfMax]);} }
-
时间复杂性分析
- 比较次数(只考虑 a 数组中的元素的比较)
最好情况下(即序列已经有序):只需进行 if 判断即可,故 comparebest=(n−1)次compare_{best} = (n - 1)\text{次}comparebest=(n−1)次
最坏情况下:每次都要进行 if 判断,共需 compareworst=n−1+n−2+⋯+1=n(n−1)2次compare_{worst} = n - 1 + n - 2 + \cdots + 1 = \frac{n(n - 1)}{2}\text{次}compareworst=n−1+n−2+⋯+1=2n(n−1)次 - 交换次数
最好情况下:只需进行一次交换即可(即第一次的时候),故swapbest=1次swap_{best} = 1\text{次}swapbest=1次
最坏情况下:每次都需要进行交换,故需 swapworst=(n−1)次swap_{worst} = (n - 1) \text{次}swapworst=(n−1)次
- 比较次数(只考虑 a 数组中的元素的比较)
-
冒泡排序
- “最原始的” 冒泡排序
-
主要思路及实现
- 步骤一:每次对 n−i(i∈0,1,2,⋯,n−1)n - i(i \in {0, 1, 2, \cdots, n - 1})n−i(i∈0,1,2,⋯,n−1) 个元素集中的相邻元素进行比较,若不满足顺序,则交换;反之保持原样。每次冒泡过程结果为对应数据集中的最大值被放到序列的最右端;
- 步骤二:重复步骤一 n - 1 次即可;
具体实现:
//冒泡排序 //一次冒泡过程 template<typename T> void bubble(T* a, int n) //一次冒泡过程 {for (int i = 0; i < n; ++i){if (a[i] > a[i + 1])swap(a[i], a[i + 1]);} }template<typename T> void bubbleSort(T* a, int n) {for (int i = n; i > 0; --i)bubble(a, i); }
-
时间复杂性分析
- 比较次数(针对 a 数组中的元素)
这样的比较主要发生在一次冒泡过程中,每一次都有 n−i(i∈0,1,2,⋯,n−1)n - i (i \in {0, 1, 2, \cdots, n - 1})n−i(i∈0,1,2,⋯,n−1) 进行比较,因此:compare=n−1+n−2+⋯+2+1=n(n−1)2次compare = n - 1 + n - 2 + \cdots + 2 + 1 = \frac{n(n - 1)}{2} \text{次}compare=n−1+n−2+⋯+2+1=2n(n−1)次 - 交换次数
最好情况下,所有元素开始就已经有序,无需交换,故:comparebest=0次compare_{best} = 0\text{次}comparebest=0次
最坏情况下,所有元素都得两两进行比较,故:compareworst=n−1+n−2+⋯+2+1=n(n−1)2次compare_{worst} = n - 1 + n - 2 + \cdots + 2 + 1 = \frac{n(n - 1)}{2}\text{次}compareworst=n−1+n−2+⋯+2+1=2n(n−1)次
- 比较次数(针对 a 数组中的元素)
-
- “及时终止的” 冒泡排序
-
主要思路及实现
- 与 “及时终止的” 选择排序一样,也是用一个 bool 值变量在 if 循环中控制循环的进行;
具体实现:
//及时终止的冒泡排序 template<typename T> void improved_bubbleSort(T* a, int n) {bool stopped = false;for (int i = n; !stopped && i > 0; --i){stopped = true;for (int j = 0; j < i; ++j){if (a[j] > a[j + 1])swap(a[j], a[j + 1]);elsestopped = false;}} }
-
时间复杂性分析
- 比较次数
最好情况下,只需在第一趟冒泡时进行元素的比较即可,因此:comparebest=(n−1)次compare_{best} = (n - 1)\text{次}comparebest=(n−1)次
最坏情况下,每一次都会进行比较,因此:compareworst=n(n−1)2次compare_{worst} = \frac{n(n - 1)}{2}\text{次}compareworst=2n(n−1)次 - 交换次数
交换次数和 “最原始的” 冒泡排序一样,并无变化。
- 比较次数
-
插入排序
-
主要思路及实现
- 步骤一:假设第 kkk 个元素 x 之前的序列是有序的,将第 kkk 个元素 x 它们进行比较;
- 步骤二:若 x < a[i],则 a[i] 及之后的元素都向后移,直接某个元素 < x,那么 x 就应该插入在第 i + 1 个位置;
具体实现:
//插入排序 template<typename T> void insert(T* a, int n, int key) {int i;for (i = n - 1; i >= 0 && key < a[i]; --i)a[i + 1] = a[i];a[i + 1] = key; }template<typename T> void insertSort(T* a, int n) {for (int i = 1; i <= n; ++i)insert(a, i, a[i]); }
- 时间复杂性分析
- 比较次数
最好情况下,只需与最后一个元素进行比较即可,故 comparebest=n−1次compare_{best} = n - 1\text{次}comparebest=n−1次
最坏情况下,每个元素均需进行比较,故 compareworst=n−1+n−2+⋯+2+1=n(n−1)2次compare_{worst} = n - 1 + n - 2 + \cdots + 2 + 1 = \frac{n(n - 1)}{2}\text{次}compareworst=n−1+n−2+⋯+2+1=2n(n−1)次 - 移动次数
最好情况下,只有在传参和最后 key 的赋值时进行了移动,因此:movebest=2(n−1)次move_{best} = 2(n - 1)\text{次}movebest=2(n−1)次
最坏情况下,在 comparebestcompare_{best}comparebest 的基础上,还要进行 a 数组元素向后移的操作,因此:moveworst=movebest+n−1+n−2+⋯+2+1=2(n−1)+n(n−1)2次move_{worst} = move_{best} + n - 1 + n - 2 + \cdots + 2 + 1 \\= 2(n - 1) + \frac{n(n - 1)}{2}\text{次}moveworst=movebest+n−1+n−2+⋯+2+1=2(n−1)+2n(n−1)次
- 比较次数
后续的排序方法还会进行补充!