排序基本概念
- 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
- 不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
直接插入排序(重点)
规则:选取一个有序序列,从待排序序列中一个一个选择最小的插入到已经排序好的有序序列中取(类似扑克牌)。
// 直接插入排序
void insert(int arr[] , int n){
int temp , i , j;
for(i = 1 ; i < n ; ++i){
temp = arr[i]; // 这里i表示未排序元素,从前向后遍历
j = i - 1;
while(j >= 0 && temp < arr[j]){
arr[j+1] = arr[j];
--j;
}
arr[j + 1] = temp; // 元素插入排序序列当中
}
}
简单选择排序(重点)
规则:整体上看作是一个无序序列,遍历找到整个序列中最小的,与无序序列的第一个元素交换位置,不断重复完成排序。
// 简单选择排序
void select(int arr[] , int n){
int i , j , k;
int temp;
for(i = 0 ; i < n ; ++i){ // 遍历全体元素
k = i;
for(j = k + 1 ; j < n ; ++j){ // 从无序序列中找到最小值
if(arr[k] > arr[j]){
k = j;
}
}
temp = arr[i]; // 最小值与徐徐序列的首位元素交换位置
arr[i] = arr[k];
arr[k] = temp;
}
}
冒泡排序(重点)
规则:从前向后,两两比较,若前面一个大于后面一个,则交换彼此的位置。一轮后最大的元素被放在后面,重复即可。
优化:设置一个标记flag,当遍历所有无序序列都没有产生交换操作时,说明整个序列已经有序,不需要后面继续冒泡排序了,则退出排序过程。
// 冒泡排序
void buble(int arr[] , int n){
int i , j , flag;
int temp;
for(i = n - 1 ; i >= 1 ; --i){ // 记录剩余未交换元素数量
flag = 0; // 标记位
for(j = 1 ; j <= i ; ++j){
if(arr[j - 1] > arr[j]){
temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
flag = 1;
}
}
if(flag == 0) // 无交换则冒泡排序可以提前结束
return 0;
}
}
快速排序(重点)
规则:先确定一个枢纽A(一般取第一个元素),以A为中心,划分成左边的都小于A,右边的都大于A。划分结束后,再分别对A的左右两侧序列递归的进行划分操作。
// 快速排序
void quick(int arr[] , int low , int high){
int temp;
int i = low;
int j = high;
if(low < high){ // 递归出口
temp = arr[low]; // 默认中间值为第一个元素
while(i < j){
while(j > i && arr[j] >= temp)
--j;
if(i < j){
arr[i] = arr[j];
++i;
}
while(i < j && arr[i] < temp)
++i;
if(i < j){
arr[j] = arr[i];
--j;
}
}
arr[i] = temp; // 最后将中间值放在i,j的交汇处
quick(arr , low , i - 1); // 递归前半部分序列
quick(arr , i + 1 , high); // 递归后半部分序列
}
}
希尔排序(重点)
直接插入排序算法的改进。
特点:当待排序序列越有序,则直接插入排序的性能越好。
增量设为n / 2,每次对这些增量指向的元素进行直接插入排序。增量不断除二减少(向下取整),直到变成1,这时候就是纯粹的直接插入排序了。
这种方式使得原有序列不断的有序化,减少最终直接插入排序的移动次数。
堆排序(重点)
大顶堆:根结点大于左右孩子结点
小顶堆:根结点小于左右孩子结点
堆是完全二叉树
采用顺序存储方式
根结点为i,左孩子则为2i + 1,右孩子则为2i + 2
最后一个非叶子结点的编号:n / 2 - 1
建堆:对大顶堆,大结点向上,小结点向下;小顶堆则反之。
插入结点:先插入到最后一个编号的位置,再将其与所有的父结点看成一条序列,类似于冒泡排序,向上两两比较,直到遇到父结点大于插入结点的时候,停止移动。
第二种方式:从最后一个父结点开始,与其子节点比较;最后一层的父结点比较结束后,再向上一层比较。
删除结点:将最后一个结点放在删除结点位置处,再按照建堆的操作重新进行调整。
例题:按照下列的数据表作为输入,构造一个大顶堆。
(100,20,40,30,15,18,35,70,150,60,75,110,200,5)
归并排序
归并排序是建立在归并操作上的一种有效的排序算法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
基数排序
计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
规则:
<1>将位数补为相同,缺位高位补0;
<2>收集:先从个位开始比较,准备**十个桶(由基数决定)**按个位上的数字收集;
<3>整理:从左向右,从底下出来排列;
<4>再按十位上的数字,从左向右收集;
<5>重复步骤,直至最高位收集结束后,再整理即可得到升序序列。
稳定性
冒泡排序:稳定
简单选择排序:
<1>基于交换实现(顺序表):不稳定
<2>基于插入(链表):稳定
直接插入排序:稳定
快速排序:不稳定
希尔排序:不稳定
归并排序:稳定
堆排序:不稳定
基数排序:稳定
外部排序
- 多路归并排序:利用外存储器存数据,挑选部分数据进入内存进行多路归并排序。
- 置换-选择排序:通过外存一个一个的读入,可以得到一个个可归并的升序序列;当待出元素小于外面已排好序列的最值,则标记后接另一个元素进行比较,重复上述操作。
- 败者树:选择最值元素的方法。