十大排序算法
参考:https://www.cnblogs.com/linuxAndMcu/p/10201215.html
零、主代码
#include <iostream>
#include <ctime>
const int MaxSize = 10;
void init(int arr[], int length)
{
for (int i = 0; i < length; i++)
{
arr[i] = rand() % 20;
}
}
void showArr(int arr[], int length)
{
for (int i = 0; i < length; i++)
{
printf("%d ", arr[i]);
}
printf("\n=================\n");
}
int main(int argc, char **argv)
{
// srand((unsigned int)time(nullptr));
srand(20); //固定随机种子
int arr[MaxSize];
init(arr, MaxSize);
showArr(arr, MaxSize);
// bubSort(arr, MaxSize);
// showArr(arr, MaxSize);
bubSortPro(arr, MaxSize);
showArr(arr, MaxSize);
system("pause");
return 0;
}
一、冒泡排序
1.1 基本流程
一个基于交换的排序,每一轮将序列中最大值放在序列的尾部,相邻两个比较交换,每一轮length减一
1.2代码
优化前:
void bubSort(int arr[], int length)
{
while (length--)
{
for (int i = 0; i < length; i++)
{
if (arr[i + 1] < arr[i])
{
int temp = arr[i + 1];
arr[i + 1] = arr[i];
arr[i] = temp;
}
}
}
}
优化后:
/**
* @brief 冒泡排序优化,加个交换的flag
*/
void bubSortPro(int arr[], int length)
{
// std::cout<<length<<std::endl;
int flag = 1;
while (length-- && flag) //算里面的表达式值
{
// std::cout<<length<<std::endl;
flag = 0;//每一轮重置
for (int i = 0; i < length; i++)
{
if (arr[i + 1] < arr[i])//如果一直不进入下面的if,表示截止之前的一直已经是正序了
{
flag = 1;
int temp = arr[i + 1];
arr[i + 1] = arr[i];
arr[i] = temp;
}
}
}
}
说明:优化后的效率更高:当序列在找到所有的最大值之前就已经排好序
二、选择排序
选择排序的基本思想还是冒泡排序
2.1 step1:
i ,j;j从i开始向后遍历,找到最小的记作k,找到最小的之后再将i与k进行交换
2.2 step2:
i+1开始向后重复setp1过程
2.3 代码
void selectSort(int arr[], int length)
{
for (int i = 0; i < length; i++)
{
int k = i;
for (int j = i + 1; j < length; j++)
{
if (arr[j] < arr[k])
{
k = j;
}
}
int temp = arr[i];
arr[i] = arr[k];
arr[k] = temp;
}
}
三、插入排序
优点,当原始序列已经基本有序的时候再将一个新的数据插入进来比较方便,也比较高效
有序的后面一个插入到有序中去
3.1 代码
void insertSort(int arr[], int length)
{
for (int i = 1; i < length; i++) //从1开始:因为第一个数是保障有序的
{
for (int j = i; j >= 1 && arr[j] < arr[j - 1]; j--)
{
int temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
}
}
}
四、希尔排序
优化版的插入排序
相邻两个–>相邻一大块
原来插入排序的步长为1,而希尔排序的排序步长一开始可以很大,后来逐步减小知道1形成插入排序
4.1 流程
按步长进行插入排序
变成
然后进行第二组
。。。。。。
最后
然后缩小步长
4.2 代码
void shellSort(int arr[], int length)
{
int h = 1;
int t = length / 3;
while (h < t)
{
h = 3 * h + 1;
}
while (h >= 1)
{
for (int i = 1; i < length; i++) //从1开始:因为第一个数是保障有序的
{
for (int j = i; j >= 1 && arr[j] < arr[j - h]; j = j - h)
{
int temp = arr[j];
arr[j] = arr[j - h];
arr[j - h] = temp;
}
}
h /= 2;
}
}
h=1,4,10,13…,3*n+1
为数组长度的1/3左右
五、快速排序(******)
冒泡排序的优化版
核心思想,使用轴,每一轮递归后,把轴放在中间,使得轴的左边都比轴小,轴的右边都比轴大
找中位数
所有递归结束就自然排好序了
pivot(轴)以第一个开始找
从右边开始向左找到第一个比轴(pivot=20)小的找到了5;
5替换掉20的位置
i向右找到第一个比轴(pivot=20)大的数,比如找到29覆盖掉j的位置
然后j继续走知道i和j相遇时将pivot=20填进相遇的位置
(j向左继续找比pivot小的和i替换,i向右继续找大的和j替换)
直到i和j相等
确定轴的位置后左右分别递归
5.1 左右递归
5.2代码
void quickSort(int arr[], int left, int right)
{
//退出条件
if (left >= right)
return;
int i = left;
int j = right;
int pivot = arr[i];
while (i < j)
{
while (i < j && arr[j] >= pivot)
{
j--;
}
arr[i] = arr[j];
while (i < j && arr[i] <= pivot)
{
i++;
}
arr[j] = arr[i];
}
arr[i] = pivot;//相遇了,轴的位置赋给arr[i]---将轴补上去
quickSort(arr, left, i - 1);
quickSort(arr, i + 1, right);
}
5.3面对链式存储
/**
* @brief 适用于链式存储
* 找比pivot大的区间,找比pivot小的区间
*
*/
void quickSort2(int arr[], int left, int right)
{
if (left >= right)
return;
int pivot = arr[left];
int i = left + 1;
int j = left + 1;
while (j <= right)
{
if (arr[j] < pivot)
{
myswap(arr, i, j);
i++;
}
j++;
}
myswap(arr, left, i - 1);//中间
quickSort2(arr, left, i - 2);
quickSort2(arr, i, right);
}
归并排序
基于分而治之的思想,拿两个已经有序的序列重新组合成一个新的有序序列
代码
void mergeSort(int a[], int alen, int b[], int blen, int *temp)
{
int i = 0;
int j = 0;
int k = 0;
while (i < alen && j < blen)
{
if (a[i] < b[j])
{
temp[k] = a[i];
k++;
i++;
}
else
{
temp[k] = b[j];
k++;
j++;
}
}
while (i < alen)
{
temp[k] = a[i];
k++;
i++;
}
while (j < blen)
{
temp[k] = b[j];
k++;
j++;
}
}
高级归并
一个数组分裂成几个小数组直到数组长度为1
最终程序
void merge(int arr[], int low, int mid, int height, int *temp)
{
int i = low;
int j = mid + 1;
int k = low;
while (i <= mid && j <= height)
temp[k++] = arr[i] < arr[j] ? arr[i++] : arr[j++];
while (i <= mid)
temp[k++] = arr[i++];
while (j <= height)
temp[k++] = arr[j++];
for (i = low; i <= height; i++)
arr[i] = temp[i];
}
/**
* @brief 分割,递归子排序
*
*/
void merge_sort(int arr[], int low, int height, int *temp)
{
if (low >= height)
return;
int mid = low + (height - low) / 2;
merge_sort(arr, low, mid, temp);
merge_sort(arr, mid + 1, height, temp);
merge(arr, low, mid, height, temp);
}
void mergeSort(int arr[], int length)
{
//先申请空间
int *temp = (int *)malloc(sizeof(int) * length);
assert(temp);
merge_sort(arr, 0, length - 1, temp);
free(temp);
}
堆排序
堆结构:本质上是一个完全二叉树,每个节点的存储都是连续的
知道当前下标为current,其左子树下标为2current+1,其右子树下标为2current+2.(从零开始计数的情况下,如果是从1开始计数就需要)
如果上述是从1开始计数,其左子树下标为2current,右子树下标为2current+1
小顶堆
父亲的权值比左右子树的权值小
大顶堆
父亲的权值比左右子树的权值大
小顶堆、大顶堆
外堆排序
需要申请和原来数组一样大小的内存空间, 这段内存空间是用来存储堆结构的
内堆排序
不需要重新申请内存,直接再原来的数组上进行排序
入堆(从顺序结构构造小顶堆)
接下来
最终
子节点一直和父节点的值进行比较直至构造小堆
构造小顶堆
Heap *creatHeap(int length)
{
Heap *heap = (Heap *)malloc(sizeof(Heap));
assert(heap);
heap->length = 0;
heap->root = (int *)malloc(sizeof(int) * length);
assert(heap->root);
return heap;
}
//入堆 //从零开始计数 2*current+1 ,2*curren+2 虽然/2 = i 和 i+1但是总是先填充左子树,再填充右子树(没有 只有右子树而没有左子树的情况)
// int parent = current/2;正确
//构造小顶堆
void pushHeap(Heap *heap, int data)
{
int current = heap->length++;
int parent = current / 2;
heap->root[current] = data;
while (parent != current)
{
if (heap->root[current] < heap->root[parent])
{
myswap(heap->root, parent, current);
current = parent; //改变一下父子的下标
parent = current / 2;
}
else
break;
}
}
出堆
出堆:拿出堆顶元素,用堆的最后一个元素进行覆盖
然后剩下的元素调整构造为堆结构:
父亲节点的左右子树找最小的一个依次推上去
结果
外堆排序代码
typedef struct Heap
{ //采用顺序存储结构
int *root;
int length; //实际适用数据与申请内存空间不同
} Heap;
//申请堆结构
Heap *creatHeap(int length)
{
Heap *heap = (Heap *)malloc(sizeof(Heap));
assert(heap);
heap->length = 0;
heap->root = (int *)malloc(sizeof(int) * length);
assert(heap->root);
return heap;
}
//入堆 //从零开始计数 2*current+1 ,2*curren+2 虽然/2 = i 和 i+1但是总是先填充左子树,再填充右子树(没有 只有右子树而没有左子树的情况)
// int parent = current/2;正确
//构造小顶堆
void pushHeap(Heap *heap, int data)
{
int current = heap->length++;
int parent = current / 2;
heap->root[current] = data;
while (parent != current)
{
if (heap->root[current] < heap->root[parent])
{
myswap(heap->root, parent, current);
current = parent; //改变一下父子的下标
parent = current / 2;
}
else
break;
}
}
//出堆 拿出堆顶元素
int popHeap(Heap *heap)
{
int value = heap->root[0];
/*调整堆*/
int current = 0;
int rchild = 2 * current + 2; //从零开始计数的右孩子下标
int small;
//覆盖一个长度减少1;//从零开始计数
heap->root[0] = heap->root[--heap->length];
//遍历子树调整结构
while (rchild <= heap->length) //带=因为左子树要和右子树比较
{
small = heap->root[rchild - 1] < heap->root[rchild] ? rchild - 1 : rchild; //找左右子树最小的
//再跟父亲节点比较
if (heap->root[small] < heap->root[current])
{
myswap(heap->root, small, current);
current = small;
rchild = 2 * current + 2;
}
else
break;
}
return value;
}
void heapSort(int arr[],int length)
{
Heap *heap = creatHeap(MaxSize);
for (int i = 0; i < MaxSize; i++)
{
pushHeap(heap, arr[i]);
}
for(int i=0;i<MaxSize;i++)
{
arr[i]=popHeap(heap);
}
free(heap->root);
}
内堆排序实现
参考链接:
https://zhuanlan.zhihu.com/p/45725214
https://blog.csdn.net/sxhelijian/article/details/50295637
https://zhuanlan.zhihu.com/p/124885051
在原来的数组上实现一个大顶堆
原来数组:
未排序的二叉树
从尾部开始做大顶堆(从尾部的小子树逐个开始)
43和5对换:形成
然后43 62 92这颗小树进行堆化
然后堆化52 44 46 ,已经是堆化的了,不用操作
然后再堆化80 92 43 62 43 5 这颗大树(相当于从底部小树逐步向上磊金字塔)
最后堆化整颗树
对大顶堆进行排序
5拿上去92下来,92就已经排除在堆外了,将剩余结构调整为大顶堆
最终
最后形成上述内堆
解释
large = rchild == length ? rchild - 1 : (arr[rchild - 1] > arr[rchild] ? rchild - 1 : rchild);
内堆排序代码
/**
* @brief 堆化:形成大顶堆
* @param current 当前下标
*/
void heapify(int arr[], int length, int current)
{
//右子树
int rchild = 2 * current + 2;
int large;
while (rchild <= length) //需要比较的
{
// rchild == length就是左子树??
large = rchild == length ? rchild - 1 : (arr[rchild - 1] > arr[rchild] ? rchild - 1 : rchild);
if (arr[large] > arr[current]) //大的就推上去
{
myswap(arr, large, current);
current = large; //移动current
rchild = 2 * current + 2;
}
else
break;
}
}
/**
* @brief 内堆排序
*
*/
void heapSortInter(int arr[], int length)
{
int current = length / 2; //???length not size //传进heapify的current相当于父亲节点
while (current >= 0)
{
heapify(arr, length, current);
current--;
}
// showArr(arr, length);
while (length > 0) //左闭右开
{
myswap(arr, 0, --length);
//交换完从零的位置进行heapify
heapify(arr, length, 0);
}
}
计数排序
基于桶思想,利用数组的天然有序
算法思想:统计原来数组的数据,并将数据转换成下标存储于一个临时的空间中,然后遍历临时空间把对应的下标值放回原数组,当遍历临时空间完成后,原来的数组就是排好序的了
例子
原数据:
遍历
遍历临时空间
代码
const int N = 200;
int temp[N]; //长度=原数组的最大值-最小值//最大值
/**
* @brief 计数排序
*
* @param arr
* @param length
*/
void countSort(int arr[], int length)
{
for (int i = 0; i < length; i++)
temp[arr[i]]++;
for (int i = 0, j = 0; i < N; i++)
while (temp[i]--)
arr[j++] = i;
}
基数排序
例子
154 423 365 251 78 92 640
第一轮(
1
0
0
10^0
100位):640 251 92 423 154 365 78
第二轮(
1
0
1
10^1
101位):423 640 251 154 365 78 92 (按十位排序,即当十位相同时按照上一轮结果)
第三轮(
1
0
2
10^2
102位):78 92 154 251 365 423 640
从左向右拿
代码
void redixSort(int arr[], int length)
{
//存入桶
int i;
int j;
int pos;
for (int k = 10; k < 10000; k *= 10)//第一二三轮
{
for (i = 0; i < length; i++)
{
j = 0;
pos = (arr[i] % k) / (k / 10);
while (Temp[pos][j])//看每个位数桶上对应的j位置是否存在数
j++;
Temp[pos][j] = arr[i];
}
//恢复
pos = 0;
for (i = 0; i < 10; i++)
{
for (j = 0; j < length && Temp[i][j] != 0; j++)
{
arr[pos++] = Temp[i][j];
Temp[i][j] = 0;
}
}
}
}