8.内部排序
8.1 插入排序
8.1.1 直接插入排序
-
思路:
①从前面的有序子表中查找出待插入元素应该被插入的位置;
②给插入位置腾出空间,将待插入元素复制到表中的插入位置。
-
代码
void InsertSort(ElemType a[], int n){ for(int i = 2; i <= n; i++){ //将a[2]~a[n]插入前面已经排好的序列 if(a[i] < a[i-1]){ //若a[i]关键码小于前驱,将a[i]插入有序表 a[0] = a[i]; //复制为哨兵,a[0]不存放元素 for(int j = i-1; a[0] < a[j]; --j) //从后往前查找插入位置 a[j+1] = a[j]; //向后挪位 a[j+1] = a[0]; //复制到插入位置 } } } -
性能分析
-
空间复杂度:仅使用了常数个辅助单元,空间复杂度为O(1)。
-
时间复杂度:
- 最好情况下,表中元素已经有序,每次插入元素都只需要比较一次而不需要移动元素,因而时间复杂度为O(n)。
- 最坏情况下,表中元素逆序,总的比较次数和移动次数达到最大,时间复杂度为O(n^2)。
- 平均情况下,时间复杂度为O(n^2)。
-
稳定性:由于每次插入元素总是从后向前先比较再移动,不会出现相同元素相对位置发生变化的情况,因此直接插入排序是一个稳定的排序方法。
-
适用性:适用于顺序存储和链式存储。为链式存储时,可以从前往后查找指定元素的位置。适用于基本有序和数据量不大的排序
8.1.2 折半插入排序
-
思路:当排序表为顺序表时,查找有序表时可以用折半查找来实现。
-
代码
void BinInsertSort(ElemType a[], int n){ int low,high,mid; for(int i = 2; i <= n; i++){ //将a[2]~a[n]插入前面已经排好的序列 a[0] = a[i]; //复制为哨兵,a[0]不存放元素 low = 1; high = i-1; //设置折半查找的范围 while(low <= high){ //折半查找 mid = (low + high)/2; //取中间点 if(a[mid] > a[0]) high = mid - 1; //查找左半子表 else low = mid + 1; //查找右边半子表 } for(int j = i-1; j >= high + 1; --j) //从后往前查找插入位置 a[j+1] = a[j]; //向后挪位 a[j+1] = a[0]; //复制到插入位置 } } } -
性能分析
-
空间复杂度:仅使用了常数个辅助单元,空间复杂度为O(1)。
-
时间复杂度:减少了比较的次数,该比较次数与待排序表的初始状态无关,仅取决于n。移动次数并未改变。
-
平均情况下,时间复杂度为O(n^2)。
-
稳定性:稳定
8.1.3 希尔排序
-
过程:
① 取一个小于n的步长d1,把表中的全部记录分成d1组,左右距离为d1倍数的元素放在同一组,在各组内进行直接插入排序;
② 然后取第二步长d2小于d1,重复①,知道所取到的步长为1,即所有记录在同一组中,再进行直接插入排序,由于此时已经具有较好的局部有序性,故可以很快得到最终结果。
-
代码
void ShellSort(ElemType a[], int n){ for(dk = n/2; dk >= 1; dk = dk/2) for(i = dk + 1; i <= n; ++i) if(a[i] < a[i - dk]){ a[0] = a[i]; for(j = i - dk; j > 0 && a[0] < a[j]; j-=dk) a[j+dk] = a[j0]; a[j+dk] = a[0]; } } -
性能分析
-
空间复杂度:仅使用了常数个辅助单元,空间复杂度为O(1)。
-
时间复杂度:依赖于增量序列的函数
-
当n在某个特定范围,时间复杂度为O(n^1.3)
-
最坏情况下, 时间复杂度为O(n^2)
-
-
稳定性:不稳定,相同关键字的记录被划分到不同的子表
-
适用性:仅适用于线性表为顺序存储的情况。
8.2 交换排序
8.2.1 冒泡排序
-
基本思想:从后往前(或从前往后)两两比较相邻元素的值,若为逆序,则交换他们,知道序列比较完。我们称为第一趟冒泡,结果是将最小的元素交换到待排序序列的第一个位置(或者将最大的元素交换到待排序列的最后一个位置)。下一趟冒泡时,前一趟所确定的最小的元素不在参与比较,没让冒泡的结果是把序列中的最小元素放到了最终的位置。这样,最多n-1趟冒泡救恩那个排好序。
-
伪代码
void BubbleSort(ElemType a[], int n){ for(int i = 0; i < n-1; i++){ flag = false; //表示本趟冒泡排序是否发生交换的标志 for(int j = n-1; j > i; j--) //一趟冒泡排序 if(a[j-1] < a[j]){ //若为逆序 swap(a[j-1],a[j]); //交换 flag = true; } if(flag == false) return ; //本趟遍历后没有发生交换,说明已经有序 } } -
性能分析
-
空间复杂度:仅使用了常数个辅助单元,空间复杂度为O(1)
-
时间复杂度:
-
最好情况下,初始序列有序,第一趟冒泡排序后,flag依然为false,比较次数n-1,移动次数为0,时间复杂度为O(n)
-
最坏情况下,初始序列为逆序,共需要n-1趟排序,第i趟要进行n-i趟关键字的比较,每次比较都需要移动三次交换元素位置。
-
比较次数n(n-1)/2,移动次数3n(n-1)/2,时间复杂度为O(n2),平均时间复杂度也为O(n2)。
-
8.2.2 快速排序
-
算法思想:快排的思想是基于分治法的:在待排序序列中任取一个元素pivot作为枢轴,通过一趟排序将待排序表划分为独立的两部分,前面的所有元素小于pivot,后面的元素都大于pivot。pivot被放在了最终的位置,这个过程称为一趟快排。然后两个子表递归重复上述过程,直至每部分只有一个元素或空为止,即所有元素被放在了最终的位置上。
-
伪代码
void QuickSort(ElemType a[], int low, int high){ if(low < high){ //跳出递归的条件 int pivotpos = Partition(a,low,high); //将待排序列划分为两个子表 QuickSort(a,low,pivotpos-1); //对两个子表进行递归排序 QuickSort(a,pivotpos+1,high); } } -
性能分析
-
空间效率:快排序是递归的,需要借助一个递归工作栈来保存每层递归调用的必要信息,容量与递归调用的最大深度一致。
-
最好情况下为O(log(2)n)
-
最坏情况下要进行n-1次递归调用,栈的深度O(n)
-
平均情况下O(log(2)n)
-
-
时间效率:快排的运行时间与划分是否对称有关,
-
最坏情况,每层递归,两个区域包含n-1个元素和0个元素,初始序列基本有序或基本逆序时,最坏情况下时间复杂度为O(n^2)
-
最理想情况下,每个做到最平衡的划分,得到的两个子序列大小都不大于n/2,在这种情况下快排的序列将大大提升,时间复杂度为O(nlog(2)n)
-
平均情况下,运行时间接近于最佳情况,时间复杂度为O(nlog(2)n)。
-
-
稳定性:不稳定
8.3 选择排序
8.3.1 简单选择排序
-
思路:每一趟在后面n-i+1个待排序元素中选择关键字最小的元素,作为有序子序列的第i个元素,知道n-1趟做完,待排序序列只剩下1个,就不中再选了。
-
伪代码
void SelectSort(ElemType a[], int n){ for(int i = 0; i < n-1; i++){ //移动进行n-1趟 min = i; //记录最小元素位置 for(j = i + 1; j < n; j++) //在a[i……n-1]中选择最小的元素 if(a[j] < a[min]) min = j; //更新最小元素的位置 if(min != i) swap(a[i],a[min]) //封装的swap()共移动元素三次 } }
3.性能分析
-
空间效率:仅使用了常数个辅助单元,空间复杂度为O(1)
-
时间效率:最好的情况,已经有序,移动0次;但元素的比较次数与序列初始状态无关,始终是n(n-1)/2次,因此时间复杂度是O(n^2)
-
稳定性:不稳定
8.3.2 堆排序
- 算法思想
- 伪代码
- 性能分析
-
空间效率:仅使用了常数个辅助单元,空间复杂度为O(1)
-
时间效率:建堆时间O(n),每次调整的时间是O(h),时间复杂度是O(n^2)
-
稳定性:不稳定
8.4 归并排序
- 伪代码
- 性能分析
-
空间效率:merge()操作中,辅助空间n个单元,空间复杂度为O(n)
-
时间效率:每趟归并的时间复杂度为O(n),共需要log(2)n趟,时间复杂度为O(nlog(2)n)。
-
稳定性:merge不会改变相同关键字记录的相对次序,是一种稳定的算法。
-
N个元素进行k路归并,排序的趟树满足k^m=N,从而m=log(k)N,向上取整。
8.5 基数排序
-
性能分析
-
空间效率:一趟排序需要的辅助空间为r(r个队列,r个头指针,r个尾指针),但以后会重复利用到这些队列,所以技术排序的空间复杂度为O(r)
- eg:0,1,2,3,4,5,6,7,8,9.r=10
-
时间效率:技术排序共需要d趟分配和收集,一趟分配需要O(n),一趟收集需要O(r),所以技术排序的时间复杂度为O(d(n+r)),它与序列的初始状态无关。
- eg:个十百千,d=4
-
稳定性:对于基数排序算法而言,很重要一点就是按位排序时是稳定的。因此,这也保证了基数排序的稳定性

浙公网安备 33010602011771号