5.1 排序的基本概念
排序是处理数据的一种最常见的操作,所谓排序就是将数据按某字段规律排列,所谓的字段就是数据节点的其中一个属性。比如一个班级的学生,其字段就有学号、姓名、班级、分数等等,我们既可以针对学号排序,也可以针对分数排序。
- 稳定性与非稳定性
稳定排序:排序前后两个相等的数相对位置不变,则算法稳定 6 6
非稳定排序:排序前后两个相等的数相对位置发生了变化,则算法不稳定
- 内排序与外排序
如果待排序数据量不大,可以一次性全部装进内存进行处理,则称为内排序,若数据量大到无法一次性全部装进内存,而需要将数据暂存外存,分批次读入内存进行处理,则称为外排序。
性能分析
不同的排序算法性能不同,详细性能数据如下表所示。
排序算法 | 平均 T(n) | 最坏 T(n) | 最好 T(n) | 空间复杂度 | 稳定性 |
选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 |
插入排序 | O(n2) | O(n2) | O(n) | O(1) | 稳定 |
希尔排序 | O(n1.3) | O(n2) | O(n) | O(1) | 不稳定 |
冒泡排序 | O(n2) | O(n2) | O(n) | O(1) | 稳定 |
快速排序 | O(nlog2n) | O(n2) | O(nlog2n) | O(nlog2n) | 不稳定 |
5.2 排序的种类
5.2.1 冒泡排序
首先引入两个概念:
- 顺序:如果两个数据的位置符合排序的需要,则称它们是顺序的。
- 逆序:如果两个数据的位置不符合排序需要,则称它们是逆序的。
冒泡排序基于这样一种简单的思路:从头到尾让每两个相邻的元素进行比较,顺序就保持位置不变,逆序就交换位置。可以预料,经过一轮比较,序列中具有“极值”的数据,将被挪至序列的末端。
假如序列中有n个数据,那么在最极端的情况下,只需要经过n−1轮的比较,则一定可以将所有的数据排序完毕。冒泡法排序的时间复杂度是O(n²)
代码实现:
//冒泡排序
void sort(int *array,int size){
for(int i = 0; i < size - 1; i++){
for(int j = 0; j < size - i - 1; j++){
if(array[j] > array[j + 1]) {//找到最大的一个数
swap(&array[j],&array[j + 1]);//交换函数
}
}
}
}
5.2.2 插入排序
插入排序的思路也很简单:假设前面已经有i节点是有序的(排好顺序),那么就从第i+1个节点开始,插入到前面的i个节点的合适的位置中。由于第一个元素自身总是有序的(假设性),因此从第2个开始,不断插入前面的有序序列,直到全部排列完毕。
代码实现:
//插入排序
void InsertSort(int *array,int size){
int tmp , j;
for(int i = 1;i < size; i++){
tmp = array[i];
for(j = i - 1; j >= 0; j--){
if(array[j] < tmp) break;
array[j + 1] = array[j];//交换函数
}
array[j + 1] = tmp;
}
}
5.2.3 选择排序
选择排序的思路非常简单,就是依次从头到尾挑选合适的元素放到前面。如果总共有n个节点,那么选择一个合适的节点需要比较n次,而总共要选择n次,因此总的时间复杂度是O(n²)
代码实现:
//选择排序
void SelectSort (int *array,int size){
for(int i = 0; i < size; i++){
int min = i;
for(int j = i + 1; j < size; j++){
if(array[j] < array[min]) min = j;
}
swap(&array[i],&array[min]);//交换函数
}
}
5.2.4 快速排序
快排是一种递归思想的排序算法,先比较其他的排序算法,它需要更多内存空间,但快排的语句频度是最低的,理论上时间效率是最高的。
快速排序的基本思路是:在待排序序列中随便选取一个数据,作为所谓“支点”,然后所有其他的数据与之比较,以从小到大排序为例,那么比支点小的统统放在其左边,比支点大的统统放在其右边,全部比完之后,支点将位与两个序列的中间,这叫做一次划分(partition)。
一次划分之后,序列内部也许是无序的,但是序列与支点三者之间,形成了一种基本的有序状态,接下去使用相同的思路,递归地对左右两边的子序列进行排序,直到子序列的长度小于等于1为止。
代码实现:
//快速排序
void Quick_sort(int *array,int left,int right){
int i,j;
if(left > right){
return;
}
int key = array[left];
i = left;
j = right;
while(i!=j){
while(array[j] >= key && i!=j){
j--;
}//从右往左找比key值小的数
while (key >= array[i] && i!=j){
i++;
}//从左往右找比key值大的数
if(i!=j){
swap(&array[j],&array[i]);
}//找到后交换数值
}
swap(&array[left],&array[j]);//i与j相遇后与key值交换
Quick_sort(array,left,j-1);
Quick_sort(array,j+1,right);
}
把数组下标0作为基准值
j先开始,j从后往前遍历找到第一个比基准值小的数,然后停下来
i后开始,i从前往后遍历找到第一个比基准值大的数,然后停下来
此时有两种情况发生
情况1:i,j停下来,但是没有相撞
i和j交换对应位置的数据即可
i和j交换对应位置的数据之后,接着j再次移动,i再次移动,移动的过程还是重复前面的操作,直到i和j想撞,把相撞位置的数据和基准值交换
情况2:i,j停下来,相撞
把相撞位置的数据和基准值交换
经过上面一轮循环,此时基准值左边的都是比它小的数据,基准值右边
的都是比它大的数据
接着左右两部分递归调用自己,重复上述过程
时间复杂度为O(nlogn)