排序
生活中我们常常会用到排序的思想,例如我们小学排座位会按照身高由低到高的顺序进行排序站位、又或如我们淘宝购物时会按照个人想法将商品进行排序最终选择物美价廉的那个等等,面对不同的问题不同的人会选择不同的排序方式,升序排列降序排列最为常见。
排序:就是将一串记录按照一定的规则进行排列起来的操作
排序的稳定性:通俗来说,排序算法的稳定性是根据待排序数据的相对位置而言的,假如在一列待排序的数据中某两个值相同的元素在排序前后的相对位置不发生变化则成当前排序算法较为稳定
例如,给定一组数据:9 1 2 5 7 4 8 6 3 5 ,当前序列包含两个数字 5 ,我们用不同的图形来进行区分:
由上图可见,排序之前,方框 5 排在 圆形 5 之前,则按照从低到高的顺序排列之后可能会产生两种情况:
第一种情况下两个 5 的相对顺序并没有发生变化,而第二种情况下两个 5 的相对顺序发生了变化,因此第一种情况下的排序称为较稳定的排序,而第二种排序称为不稳定排序。
排序的分类
(本篇先介绍插入排序、希尔排序、选择排序、堆排序)
插入排序
例如,给定一组数字,要求将其按照从小到大排序:
首先,定义两个指针 end :记录前一个数字, i : 指向当前数字,data :用来保存当前指针指向数字,如果 前一个数字大于当前数字,则当前数字插入到前一个数字之前(也可使用交换函数 Swap):
第一次插入排序之后的序列为:
实现了较大数向后移动,较小数字向前移动,排序过程:
程序:
void insertSort(int* arr, int n)
{
for (int i = 1; i < n; ++i) {
int end = i - 1; //记录已有序的最后一个元素
int data = arr[i]; //记录待排序元素
while (end >= 0 && arr[end] > data) {
arr[end + 1] = arr[end];
--end;
}
arr[end + 1] = data;
}
}
希尔排序
首先设定一个间隔 gap < n(数组长度),每进行一次交换之后修改 gap 的值,在进行交换排序,直到gap =1 所有数据都有序之后排序结束。
void shellSort(int* arr, int n)
{
//一趟希尔排序:gap 交换的间隔
int gap = n;
while (gap > 1) {
gap = gap / 3 + 1;
for (int i = gap; i < n; ++i) {
int end = i - gap;
int data = arr[i]; //记录待排序元素
while (end >= 0 && arr[end] > data) {
arr[end + gap] = arr[end];
end -= gap;
}
arr[end + gap] = data;
}
}
}
选择排序
首先写一个交换函数,交换两个给定位置的内容:
void Swap(int* arr, int pos1, int pos2)
{
int tmp = arr[pos1];
arr[pos1] = arr[pos2];
arr[pos2] = tmp;
}
方法一:每次寻找一个最小数
将数组按照从下到大顺序排列,则数组的最开始位置存放最小数字,每一次排序都是寻找待排序数据当中最小数据并将其放到最开始的位置:
起始位置时,指针的指向分别为:
进行一次交换之后:
依次类推直到所有数据有序为止(start == end 时结束)
代码:
void selectSort(int* arr, int n) //每次寻找最小的值进行交换
{
//从未排序的序列中找最值,放到未排序的起始位置
int start = 0;
int end = n - 1; //排序区间
while (start < end) {
int minIdx = start;
int i ;
for (i = start + 1; i <= end; ++i) {
if (arr[i] < arr[minIdx]) //不稳定排序 4 3 4 1
minIdx = i;
}
//把最小值放到开始的位置
Swap(arr, start, minIdx);
++start;
}
}
方法二:每次寻找一个最小数与一个最大数
实现一次交换之后:
再次重复上一步操作,寻找一个最大一个最小数,进行对应位置内容的交换,直到所有数据有序结束。
代码:
void selectSort(int* arr, int n) //每次寻找一个最小值 与 一个最大值
{
int start = 0;
int end = n - 1;
//每次寻找最大、最小两个值
//最小放头部,最大放尾部
while (start < end) {
int min = start;
int max = end;
for (int j = start + 1; j < end; ++j) {
if (arr[j] < arr[min])
min = j;
if (arr[j] > max)
max = j;
}
Swap(arr, start, min); //最小值放头部
if (max == start) //判断最大值开始时候是否在头部
max = min;
Swap(arr, end, max); //最大值放尾部
++start;
--end;
}
}
堆排序
首先建立一个大根堆(父结点大于两个孩子节点的值):
交换根结点与最后一个叶子节点的值,然后删除最后一个叶子节点,将剩余结点进行调整为大根堆:
此时,实现了第一次排序找到了最大的数值,继续重发上述操作:
得到第二个有序数据,依次类推,最终得到一个递减序列:
20 17 16 5 4 3
代码:
void shiftDown(int* arr, int n, int parent)
{ //向下调整,调整为大堆
int child = 2 * parent + 1;
while (child <= n) {
if (child + 1 < n && arr[child + 1] > arr[child])
++child;
if (arr[child] > arr[parent]) {
Swap(arr, child, parent);
parent=child;
child = 2 * parent + 1;
}
else
break;
}
}
void heapSort(int* arr, int n)
{
for (int i = (n - 2) / 2; i >= 0; --i) { //最后一个非叶子位置: (n-2)/2
shiftDown(arr, n, i); //创建大根堆
}
int end = n - 1;
while (end > 0) {
Swap(arr, end, 0);
shiftDown(arr, end, 0);
--end;
}
}
排序算法比较
插入排序:
时间复杂度:O(n^2) --------- 最坏情况下(逆序)
数据有序时,时间复杂度为 O(n) -------- 不需要排序
希尔排序
时间复杂度:O(n) ~ O(n^2)
选择排序---------不稳定排序
时间复杂度:O(n^2)
堆排序:时间复杂度:O(nlogn)