全部代码在最后
部分排序算法的理解
由小到大排序,排序数组待排序数据从1号位置开始
简单选择排序
- 从未排序序列中选择一个最小的,加入到已排好序列末尾。
直接插入排序
- 当前结点之前的序列已全部排好,若当前值小于前一个结点,说明它需要换位置,把它先存起来,然后在它之前的序列中,所有比它大的序列后移一位,再把这个结点放到空出来的位置上。
希尔排序
- 设置一个增量k,从第一个点开始,它以及与它相隔n个k位置的数看作一组,每个组内的几个数据用插入方式排好序;
- 实际实现的方式是,从第一个点加上增量k+1的点,也就是第一个分组的第二个点的位置开始,往后逐个做各自组内的插入排序,改变分组,继续排序,直到分组的增量小于1时结束。
堆排序(大根堆
- 堆是完全二叉树,所以一般用一维数组直接表示堆;
- 下标为 i 的结点的父结点下标为 i/2,其左右子结点分别为 2i、2i + 1;
- 当某结点的子孙已经是堆结构时,若它不满足堆,则对它做Heap,就是比较这个结点和它的左右孩子比较大小,找到最大的那一个与之交换, 对被交换了的子结点继续做Heap,直到交换的结点为叶子结点(结点孩子下标超过总数), 这样可以保证该结点做完heap后,包括该结点在内的往下的结构满足堆;
- 创建大根堆的过程就是从最后一个非叶子结点开始,往前逐个做Heap;
- 排序时,拿到一个大根堆,从最后一个树的结点开始,与根结点交换,交换后从树中去掉这个结点(做个标记就可以),对新的根结点做Heap,重复排序操作。
归并排序
- 将序列每一个数字看作有序组,将他与相邻的组两两一组按顺序合并,合并后的序列又是一个有序序列,继续与相邻的两两一组合并 ,直到整个序列有序,
- 递归方式,代码简洁易懂。找到中间位置,对它左右两边递归排好序,然后归并这两个串。
- 非递归更节省空间,效率也高一些。从k=1开始,将序列分好组,然后两两一组归并,每个组大小为k,起点为i,终点为i+2k-1,分割为i+k-1,i从整个数组的第一个数据开始,每次增加2k;
- 当最后剩下的数据大于一个组但是不满足两个组时(分组的起点s满足k<length-s+1<2k)
- 当最后只剩下一个组时,不改变
快速排序:
- 选取一个参考结点,把小于它的数放到它的左边,大于它的数放到右边;
- 实现方法: 对序列的头和尾两个指针,向中间靠拢,找到不满足小于/大于的位置,将参考点与之交换, 直到low和high重合,说明已经找到参考的的实际位置,然后对它左右两边的数列递归,直到整个序列都为有序
时间复杂度
代码展示
#include<iostream>
using namespace std;
#define MAXSIZE 20
typedef struct
{
int r[MAXSIZE + 1];
int length;
}SqList;
void swap(SqList* L,int i, int j)
{
int temp = L->r[i];
L->r[i] = L->r[j];
L->r[j] = temp;
}
//冒泡
void BubbleSort(SqList* L)
{
int flag = 1;
for (int i = 1; i < L->length && flag ==1; i++)
{
flag = 0;
for (int j = L->length-1; j >=i; j--)
{
if (L->r[j - 1] > L->r[j]) //从小到大排序
{
swap(L, j - 1, j);
flag = 1;
}
}
}
}
//简单选择排序:从未排序序列中选择一个最小的,加入到已排好序列末尾
void SelectSort(SqList* L)
{
int min,i,j;
for (i = 1; i < L->length; i++)
{
min = i;
for (j = i+1; j < L->length; j++)
{
if (L->r[j] < L->r[min])
min = j;
}
swap(L, min, i);
}
}
//直接插入排序:当前结点之前的序列已全部排好,若当前值小于前一个结点,说明它需要换位置,
//它先存起来,然后在它之前的序列中,所有比它大的序列后移一位,再把这个结点放到空出来的位置
void InsertSort(SqList* L)
{
int i, j;
for (i = 2; i < L->length; i++)
{
if (L->r[i] <L->r[i - 1])
{
L->r[0] = L->r[i];
for (j = i - 1; L->r[j] > L->r[0]; j--)
L->r[j + 1] = L->r[j];
L->r[j+1] = L->r[0];
}
}
}
//希尔排序:设置一个增量k,从第一个点开始,它以及与它相隔n个k位置的数看作一组,
//每个组内的几个数据用插入方式排好序,
// 实际实现的方式是,从第一个点加上增量k+1的点,也就是第一个分组的第二个点的位置开始,
// 往后逐个做各自组内的插入排序
//改变分组,继续排序,直到分组的增量小于1时结束
void ShellSort(SqList* L)
{
int i, j;
int increment = (L->length-1)/2; //增量,每次减少一半
while (increment >= 1)
{
for (i = increment + 1; i < L->length; i++)
{
if (L->r[i] < L->r[i - increment])
{
L->r[0] = L->r[i];
for (j = i - increment; L->r[j] > L->r[0]; j -= increment)
L->r[j + increment] = L->r[j];
L->r[j + increment] = L->r[0];
}
}
increment /= 2;
}
}
//堆排序(大根堆
//堆是完全二叉树,所以一般用一维数组直接表示堆,
// 下标为 i 的结点的父结点下标为 i/2,其左右子结点分别为 2i、2i + 1,
//当某结点的子孙已经是堆结构时,若它不满足堆,则对它做Heap,
// 就是比较这个结点和它的左右孩子比较大小,找到最大的那一个与之交换,
// 对被交换了的子结点继续做Heap,直到交换的结点为叶子结点(结点孩子下标超过总数),
// 这样可以保证该结点做完heap后,包括该结点在内的往下的结构满足堆
// 创建大根堆的过程就是从最后一个非叶子结点开始,往前逐个做Heap
//排序时,拿到一个大根堆,从最后一个树的结点开始,与根结点交换,交换后从树中去掉这个结点(做个标记就可以)
//对新的根结点做Heap,重复排序操作
void Heap(SqList* L, int i, int n)
{
int c1, c2;
int max = i;
c1 = 2 * i;
c2 = 2 * i + 1;
if (c2 < n && L->r[max] < L->r[c2]) //父小于右孩子
max = c2;
if (c1 < n && L->r[max] < L->r[c1]) //父小于左孩子
max = c1;
if (max != i) //最大的结点不是父节点
{
swap(L, max, i);
Heap(L, max, n);
}
else
return;
}
void HeapSort(SqList* L)
{
for (int i = L->length / 2; i > 0; i--) //先把序列建成大根堆
Heap(L, i, L->length);
for (int i = L->length - 1; i > 0; i--)
{
swap(L, i, 1); //此时已经是大顶堆,先交换树根和最后一个结点
Heap(L, 1, i); //对根结点做Heap
}
}
//归并排序
//将序列每一个数字看作有序组,将他与相邻的组两两一组合并,
// 合并后的序列又是一个有序序列,继续与相邻的两两一组合并
//直到整个序列有序
void Merge(SqList *L, int s, int m, int t) //归并函数
{
int i, j, k= s;
int LR[MAXSIZE];
for (i = s, j = m + 1; i <= m&&j <= t; k++) //这里之前犯了错误,把"&&"写成了","
{
if (L->r[i] < L->r[j])
LR[k] = L->r[i++];
else
LR[k] = L->r[j++];
}
while (i <= m)
LR[k++] = L->r[i++];
while (j <= t)
LR[k++] = L->r[j++];
for (int w = s; w <= t; w++)
L->r[w] = LR[w];
}
//递归方式,代码简洁易懂
//s/t:排序串开始/结束的下标
void MSort(SqList *L,int s,int t) //排序函数
{
int mid; //存分组中间位置
//先判断递归结束情况
if (s == t) //一个数字一组
return;
else //不相等,将串的左右部分排序,然后归并
{
mid = (s + t) / 2;
MSort(L, s, mid);
MSort(L, mid + 1, t);
Merge(L, s, mid, t);
}
}
void MergeSort1(SqList* L)
{
MSort(L,1,L->length-1);
}
//非递归
//非递归更节省空间,效率也高一些
void MergePass(SqList*L, int k, int length)
{
int i = 1, j;
while (i < length - 2 * k + 1) //i的位置足够往后划分两个k大小的组
{
Merge(L, i, i + k - 1, i + 2 * k - 1); //起始i,中间i+k-1,结束i+2*k-1
i = i + 2 * k; //下一个分组的起始
}
//剩余一个完整的k组和不完整组
//这里忽略掉只剩一个不完整组的情况,当只剩一个不完整组时,不参与调整,而且它必然时是已排好序
if (i < length - k + 1)
Merge(L, i, i + k - 1, length);
}
void MergeSort2(SqList* L)
{
int k = 1;
int* TR = (int*)malloc(L->length * sizeof(int));
while (k < L->length)
{
MergePass(L, k, L->length-1);
k = 2 * k;
}
}
//快速排序
//很重要的排序算法
//先选取一个参考结点,把小于它的数放到它的左边,大于它的数放到右边,
//对序列的头和尾两个指针,向中间靠拢,找到不满足小于/大于的位置,将参考点与之交换
// 直到low和high重合,说明已经找到参考的的实际位置
// 然后对它左右两边的数列递归,直到整个序列都为有序,最内层的情况是low==high
void QSort(SqList* L, int low, int high)
{
int pivot;
int pivotkey;
int i = low, j = high;
if (i >= j)
return;
pivotkey = L->r[low];
while (i < j)
{
//这里不要写成L->r[i] <= pivotkey,
//因为我们现在选择i作为参考,写错了i的位置永远满足条件,它不会动
while (i < j && L->r[i] <pivotkey)
i++;
while (i < j && L->r[j] > pivotkey)
j--;
swap(L, i, j);
}
pivot = i;
QSort(L, low, pivot - 1);
QSort(L, pivot + 1, high);
}
void QuickSort(SqList* L)
{
QSort(L, 1, L->length - 1);
}
int main()
{
SqList L = {{-1,3,6,2,9,10,7,8,1,5,4},11}; //L.r[0]存其他数据
cout << "排序前" << endl;
for (int i = 1; i < 11; i++)
cout << L.r[i] << " ";
// BubbleSort(&L); //冒泡
// SelectSort(&L); //简单选择
// InsertSort(&L); //直接插入
// ShellSort(&L); //希尔排序
// HeapSort(&L); //堆排序
// MergeSort1(&L); //归并排序 递归
// MergeSort2(&L); //归并排序 非递归
QuickSort(&L); //快排
cout << endl << "排序后" << endl;
for (int i = 1; i < 11; i++)
cout << L.r[i] << " ";
}