一、排序的概述
1.什么是排序?
使表中的元素按关键字有序的过程。如order by id desc
2.排序算法的评价指标
时间复杂度。空间复杂度。稳定性。
算法稳定性:关键字相同的元素在排序之后相对位置保持不变,即是稳定的;否则不稳定。
稳定的算法不一定比不稳定的好,看实际需求。
3.排序算法的分类
内部排序:数据都在内存中,如对数组进行排序。关注如何优化时间和空间复杂度
外部排序:数据太多,无法放入内存。如一个超过8G的大文件无法全部放入内存。关注如何使得读写磁盘次数更少。
二、插入排序
1.插入排序
A 算法思想
每次将一个待排序的记录按其关键字大小插入到前面已排好序的子序列中,直到全部记录插入完成。
B 算法实现
从第二个元素开始遍历,如果遍历的当前元素小于其前驱元素,则子序列中大于该元素的序列后移,将当前元素插入到指定位置。
带哨兵的插入算法:a[0]不存放元素,而是存放每次遍历的元素,这样在移动元素时,可以不用考虑指针越界问题,少写一步j >=0。
/* * 直接插入排序(不带哨兵) * 存放序列的数组a,与序列长度n */ void InsertSort(int a[],int n) { for (int i=1;i<n;i++) { //从第二个元素开始遍历 if (A[i] < A[i-1]) //如果遍历的元素小于字序列的最后一个元素,需要插入到指定位置 { int temp = A[i]; //暂存A[i] //大于temp之后的元素全部后移 for(int j=i-1; j>=0 && A[j]>temp; --j) { //所有大于temp的元素向后挪位 A[j+1] = A[j]; } A[j+1] = temp; //赋值到插入位置 } } } /** * 直接插入排序(带哨兵) * 存放序列的数组a,序列长度n,数组下标从1开始,0空出作为哨兵 */ void InsertSort(int a[],int n) { for (int i=2;i<=n;i++) { //从第二个元素开始遍历 , a[0]空出不存元素 if (A[i] < A[i-1]) //如果遍历的元素小于字序列的最后一个元素,需要插入到指定位置 { A[0] = A[i]; //复制为哨兵,A[0]不存元素 for(int j = i-1;A[0] < A[j]; --j) { //不用每次都判断j>=0 A[j+1] = A[j]; } A[j+1] = A[0]; //赋值到插入位置 } } }
C 算法效率分析
空间复杂度:定义的辅助变量都是常数级,与问题规模n无关,所以空间复杂度为O(1)
时间复杂度:主要来自对比关键字,移动元素。
若元素已经有序,最好情况,从第二个元素开始,需遍历n-1次,时间复杂度为O(n)。
若元素全部逆序,最坏情况,从第二个元素开始,每次遍历都需要移动元素,时间复杂度为O(n^2)。
平均复杂度为O(n^2),算法稳定。
顺序查找找到插入位置,可以用顺序表或链表。
2.优化-折半插入排序
先用折半查找找到应该插入的位置,再移动元素。
分析:比起直接插入,比较关键字次数减少,但是移动次数未变,所以时间复杂度依然是O(n^2)。算法稳定。
折半查找找到指定位置,所以仅适用于顺序表。
3.希尔排序
三、交换排序
基于“交换”的排序:根据序列中的两个元素关键字的比较结构来对换这两个记录在序列中的位置。
1.冒泡排序
A 算法思想(牢记)
从后往前(从前往后)两两比较相邻元素的值,若为逆序(A[i-1] > A[i]),则交换它们,直到序列比较完,完成一趟交换排序。对于每一趟最小的数像泡泡一样冒到最后,所以又叫冒泡排序。
B 过程分析
第一趟:27 < 49 不交换 =》 13<27 不交换 =》 76 >13交换 =》 97>13 交换 依次往下。
元素13逐渐“冒泡”到第一个元素位置。
第二趟
第三趟,直到某趟遍历未发生元素交换,说明表已经有序。
C 代码实现
void BubbleSort(int a[],int n)
{
for(int i=0;i<n-1;i++) {
bool flag = flase;
for (int j=n-1;j>i;j--) {
if (a[j-1] > a[j]) {
swap(a[j-1],a[j]);
flag = true;
}
}
if (!flag) { //本趟遍历未发生交换 表已经有序
return ;
}
}
}
D 算法分析
2.快速排序
四、选择排序
1.简单选择排序
2.堆排序