文章目录
一、排序分类
1.根据排序方式不同分
插入排序 | 直接插入排序、折半插入排序、希尔排序 |
---|---|
交换排序 | 冒泡排序、快速排序 |
选择排序 | 简单选择排序、堆排序 |
归并排序 | 2-路归并排序 |
基数排序 |
2.根据自然性分
自然排序:输入数据越有序,排序速度越快
非自然能排序:不是自然排序的排序方法
3.根据辅助空间分
原地排序:辅助空间超过O(1)的排序方法
非原地排序:辅助空间超过O(1)的排序方法
4.根据操作方法分
比较排序 | 插入排序、交换排序、选择排序、归并排序 |
---|---|
基数排序 | 不使用比较的方法,仅仅根据元素本身的取值确定有序位置 |
5.根据比较器的个数分
串行排序:单处理机(同一时刻比较一对元素)
并行排序:多处理机(同一时刻比较多对元素)
二、插入排序
1.直接插入排序
直接插入是一个一个元素对比,找到位置后,将元素插入,插入位置后面的元素向后移动
代码表示:
void test(vector<int>& nums)
{
for(int i = 1; i < nums.size(); i++)
{
int temp = nums[i];
for(int j = i - 1; j >= 0; j++)
{
if(nums[j] < nums[i])
{
nums[j+1] = temp;
break;
}
nums[j+1] = nums[j];
}
}
}
2.折半插入排序
和直接插入不同的是,折半插入排序不是一个一个元素对比,而是使用二分查找,找到插入位置。找到位置后,将元素插入,插入位置后面的元素向后移动
#include <iostream>
using namespace std;
void insertionSort(vector<int>& nums, int n) {
for (int i = 1; i < nums.size(); ++i) {
int aux = nums[i];
int mid;
int left = 0, right = i - 1;
while (left <= right) {
mid = left + (right - left) / 2;
if (aux < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
}
// left 为待插入的点
for (int j = i; j > left; --j) {
arr[j] = arr[j - 1];
}
arr[left] = aux;
}
}
3.希尔排序
(1)基本思想:
先将整个待排记录序列分割成若干子序列,分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序
(2)优点
缩小增量
多遍插入排序
(3)代码表示
//其中nums是需要排序的数组
//d是希尔分列时的增量,即每个多少间距,选择一个元素
//t是d的长度,即需要排列的次数
void ShellSort(vector<int>& nums, vector<int>& d, int t)
{
for(int i = 0; i < t; i++)
{
Shellsert(nums,d[i]);
}
}
void Shellsert(vector<int>& nums, int db)
{
for(int i = db + 1; i < nums.size(); i++)
{
int temp = nums[i];
for(int j = i - db; j >= 0;)
{
if(nums[j] < nums[i])
{
nums[j + db] = temp;
break;
}
nums[j + db] = nums[j];
j = j - db;
}
}
}
注意:希尔排序是不稳定排序
再排序过程中希尔排序改变了相同大小元素间的相对排序位置,如下面例子中两个8的相对位置发生改变。
希尔排序的时间复杂度由数组个数和排序次数决定,通常为:
希尔排序的最后一个增量必须是1,无除1之外的公因子
不易在链式存储结构上实现
二、交换排序
1.冒泡排序
1、流程:
将每个元素和数组中所有元素进行对比,如果当前下标元素比其后续的元素大,则进行交换。全部遍历后,实现升序排列
2、例子
通过冒泡排序方法,对数组进行升序排列
void paixu(vector<int>& nums)
{
if(nums.size() == 0)
return;
for(int i = 0; i < nums.size(); i++)
{
for(int j = i + 1; j < nums.size(); j++)
{
if(nums[j] > nums[i])
{
swap(nums[i], nums[j]);
}
}
}
}
注意:
1)双层循环,复杂度为n^2
2. 快速排序
1)基本思想
通过一次排序,将待排序的数组分为了两个独立部分,一部分记录的元素比另一部分记录的关键则小,可分别对这两部分记录进行排序,以达到整个序列有序
2)实现流程
(1)选择一个中间数作为参考,所有的元素与之比较,小的调到其左边,大的调到其右边
(2)中间数:中间数是任选的,通常是第一个元素,最后一个元素或者最中间的一个数
(3)多次重复第一步和第二步,只到获得排序数组
3)代码表示
//其中l表示的是nums的左边界
//r表示的是nums的右边界
void quick_sort(vector<int> &nums, int l, int r) {
if (l + 1 >= r) {
return;
}
//这里面选择了第一个元素作为中间数
int first = l, last = r - 1, key = nums[first];
while (first < last){
while(first < last && nums[last] >= key) {
--last;
}
nums[first] = nums[last];
while (first < last && nums[first] <= key) {
++first;
}
nums[last] = nums[first];
}
nums[first] = key;
quick_sort(nums, l, first);
quick_sort(nums, first + 1, r);
}
快速排序不是稳定排序算法
三、选择排序
1.简单选择排序
1)基本思想:在待排序的数组中选择最大(小)的元素放在其最终位置
2)实现流程:
(1)首先通过n-1次关键字比较,从n个记录中找到关键字最小的记录,将其与第一个记录交换
(2)再通过n-2次比较,从剩余的n-1次记录中找到关键字次小的记录,将它与第二个记录交换
(3)重复上卖弄操作,进行n-1次排序后,结束
3)代码表示
void SelectSort(vector<int>& nums)
{
for(int i = 0; i < nums.size(); i++)
{
int min_val = nums[i];
int k = i;
for(int j = i + 1; j < nums.size(); j++)
{
if(nums[j] < min_val)
{
min_val = numa[j];
k = j;
}
}
swap(nums[i], nums[k]);
}
}
这种方式,无论排序处于什么状态,选择排序所需进行的比较次数相同
因为每次从后面选择一个最小值,和第i个位置进行交换,
选择排序是不稳定排序
2.堆排序
1)堆定义:
从堆的定义可以看出,堆是满足二叉树中任一非叶子结点均小于(大于)它孩子结点 的完全二叉树
2)堆排序定义:
若在输出堆顶的最小值(最大值)后,使得剩余n-1个元素的序列重新又建成一个堆,则得到n个元素的次小值(次大值),… 如此反复,便能得到一个有序序列,这个过程叫堆排序
3)堆的调整:
以小根堆为例,介绍堆排序的过程
(1)输出堆顶元素后,以堆中最后一个元素替代
(2)将根节点值与左、右子树的根结点值进行比较,并与其中小者进行交换
(3)重复上述操作,直到叶子结点,将得到新的堆,称这个从堆顶至叶子的调整过程为筛选
4)代码表示:
//s和m表示的是需要排序的数组部分
//以下是建堆代码
void HeapAdjust(vector<int>& nums, int s, int m)
{
int temp = nums[s];
for(int i = 2 * s; i <= m; i *= 2)
{
//这步是判断左右子树那个元素大,如果右子树大,那么变换到右子树上
if(i < m && nums[i] < nums[i+1])
i++;
if(temp >= nums[i])
{
break;
}
nums[s] = nums[i];
s = i;
}
nums[s] = temp;
}
//以下是堆排序代码
void HeapSort(vector<int>& nums)
{
int i;
for(i = nums.size() / 2; i >= 0; i-- )
{
HeapAdjust(nums, i, nums.size() - 1);
}
//i不等于0是因为下表0对应的元素已经是最小了,不需要排序了
for(i = 1; i < nums.size(); i++)
{
swap(nums[i], nums.back());
HeapAdjust(nums,i,nums.size() - 1);
}
}
三、归并排序
将数组分为左右两部分,分别排序再合并
1.代码表示
void merge_sort(vector<int> &nums, int l, int r, vector<int> &temp) {
if (l + 1 >= r) {
return;
}
// divide
int m = l + (r - l) / 2;
merge_sort(nums, l, m, temp);
merge_sort(nums, m, r, temp);
// conquer
int p = l, q = m, i = l;
while (p < m || q < r) {
if (q >= r || (p < m && nums[p] <= nums[q])) {
temp[i++] = nums[p++];
} else {
temp[i++] = nums[q++];
}
}
for (i = l; i < r; ++i) {
nums[i] = temp[i];
}
}
四、基数排序
基数排序又叫桶排序
1.基本思想:
分配+收集
设置若干个箱子,将关键字为k的记录放入第k个箱子,然后再按序号将非空的链接
2.代码表示
假设原来有一串数值如下所示:
73, 22, 93, 43, 55, 14, 28, 65, 39, 81
首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:
0
1 81
2 22
3 73 93 43
4 14
5 55 65
6
7
8 28
9 39
第二步:
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
81, 22, 73, 93, 43, 14, 55, 65, 28, 39
接着再进行一次分配,这次是根据十位数来分配:
0
1 14
2 22 28
3 39
4 43
5 55
6 65
7 73
8 81
9 93
第三步:
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
14, 22, 28, 39, 43, 55, 65, 73, 81, 93
这时候整个数列已经排序完毕;如果排序的对象有三位数以上,则持续进行以上的动作直至最高位数为止。
/*
* 打印数组
*/
void printArray(int array[],int length)
{
for (int i = 0; i < length; ++i)
{
cout << array[i] << " ";
}
cout << endl;
}
/*
*求数据的最大位数,决定排序次数
*/
int maxbit(int data[], int n)
{
int d = 1; //保存最大的位数
int p = 10;
for(int i = 0; i < n; ++i)
{
while(data[i] >= p)
{
p *= 10;
++d;
}
}
return d;
}
void radixsort(int data[], int n) //基数排序
{
int d = maxbit(data, n);
int tmp[n];
int count[10]; //计数器
int i, j, k;
int radix = 1;
for(i = 1; i <= d; i++) //进行d次排序
{
for(j = 0; j < 10; j++)
count[j] = 0; //每次分配前清空计数器
for(j = 0; j < n; j++)
{
k = (data[j] / radix) % 10; //统计每个桶中的记录数
count[k]++;
}
for(j = 1; j < 10; j++)
count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶
for(j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中
{
k = (data[j] / radix) % 10;
tmp[count[k] - 1] = data[j];
count[k]--;
}
for(j = 0; j < n; j++) //将临时数组的内容复制到data中
data[j] = tmp[j];
radix = radix * 10;
}
}
int main()
{
int array[10] = {73,22,93,43,55,14,28,65,39,81};
radixsort(array,10);
printArray(array,10);
return 0;
}
//结果
//14 22 28 39 43 55 65 73 81 93