考研数据结构chap8排序

目录

一、概念

1.评价

(1)稳定性

(2)Tn、Sn

2.分类

(1)内部排序

(2)外部排序

二、插入排序

1.直接插入排序(InsertSort)

(1)思路

(2)实现

(3)性能分析

1)Tn

2)Sn

3)稳定性

4)适用性

2.折半插入排序

(1)思路

(2)实现

(3)性能分析

1)Tn

best:O(n^2)

2)Sn

3)稳定性

4)适用性

3.希尔排序(ShellSort)(手算)

(1)背景

(2)思路

(3)实现

(4)性能分析

1)Tn

2)Sn

3)稳定性

4)适用性

三、交换排序

1.冒泡排序(BubbleSort)

(1)思路

(2)实现

(3)性能分析

1)Tn

2)Sn

3)稳定性

4)适用性

2.快速排序(QuickSort)

(1)思路

(2)实现

(3)性能分析

1)Tn

2)Sn

3)稳定性

4)适用性

(4)一次划分 vs 一趟排序

四、选择排序

1.简单选择排序

(1)思想

(2)实现

2.堆排序(以大根堆为eg)

(1)思想

(2)实现

五、归并排序(MergeSort)

1.基本概念

2.思路

3.实现

4.性能分析

(1)Tn

(2)Sn

(3)稳定性

六、基数排序(RadixSort)

1.手算

2.思路

(1)初始化

(2)分配

(3)收集

3.效率分析

(1)Tn

(2)Sn

(3)稳定性

4.application

(1)可以分成的类d较少

(2)每个位置的取值r范围较小

(3)总数n较大

七、外部排序

1.准备

2.思路

3.k路归并

4.Tn

5.优化

(1)多路归并

(2)败者树

1)background

2)说明

3)构建

(3)置换-选择排序(手算)

1)background

2)思想

3)构建

(4)最佳归并树

1)background

2)计算

3)构建(3路平衡归并树)

4)确定虚结点个数


一、概念

1.评价

(1)稳定性

排序前后相同元素的相对位置是否改变

(2)Tn、Sn

2.分类

(1)内部排序

关注算法的Sn、Tn

(2)外部排序

还关注硬盘的读写次数尽量少

二、插入排序

1.直接插入排序(InsertSort)

(1)思路

遍历i个eles,第一个ele当作有序,从第二个ele开始,依次与前面ele比较,if <,交换

(2)实现

//不使用哨兵
void InsertSort(SqList &L,int n){
    ElemType tmp;//记录待交换结点
    for (int i = 1; i < n; ++i) {
        tmp = L->data[i];
        int j;
        for ( j = i-1 ; j >=0 && L->data[j] > tmp; j--) {
                L->data[j+1] = L->data[j];
        }
        L->data[j] = tmp;
    }
}

//有哨兵
void InsertSort2(SqList &L,int n){
    int i,j;
    for (i = 2; i < n; ++i) {
        if(L->data[i] < L->data[i-1]){
            L->data[0] = L->data[i];
        }
        for (j = i-1; L->data[0] < L->data[j]; --j) {
            L->data[j+1] = L->data[j];
        }
        L->data[j+1] = L->data[0];
    }
    L->len -=1;
}

(3)性能分析

1)Tn

对n个eles,so需要执行n-1次,主要Tn为找位置和交换,so Tn =  O(n-1 * n) ~ O(n)

best:全部有序,仅查找即可不用交换 soTn = O(n)

由此可知,在数组有序or大概有序时,直接插入排序效率较高

worst:全部逆序,查找n-1次,每次交换i-1次,so O(n^2)

avg:O(n^2)

2)Sn

O(1)

3)稳定性

当相等时设置不会交换,so稳定

4)适用性

线性表,顺序表和链表均可

2.折半插入排序

(1)思路

利用二分查找查找目标ele应该插入的pos,pos前面的ele前移

(2)实现

//定一个元素位置操作
//挖坑法
int partition(ElemType A[],int low,int high){
    ElemType pivot=A[low];//存分割值,最左边的元素
    //∵用pivot存储了定位的值,so可以直接覆盖,不交换了,effective
    //high 大 high--, 小 A[low] = A[high] low++ high--
    //间隔着换,low换完high换,high换完low换
    while(low<high){
        while(low<high && A[high]>=pivot){//找到比分割值小的元素
            high--;
        }
        A[low]=A[high];

        while(low<high && A[low] <= pivot){
            low++;
        }
        A[high] = A[low];
    }
    //找到位置了
    A[low] =pivot;

    return low;
}
void QuickSort(ElemType A[],int low,int high){
    //low最左边元素,high最右边元素
    if(low <high) {//至少2个元素
        //partition->O(n)分成两半while->O(logn)
        //O(n)O(logn)
        //最差O(n^2)
        // reason:有序的数组,递归n次,递归一次O(n)
        int pivot_pos = partition(A, low, high);//找定哪个位置
        //pivot:n.支点,中心点
        //之后再递归前一半
        QuickSort(A, low, pivot_pos - 1);
        //后一半
        QuickSort(A, pivot_pos + 1, high);
    }
}

(3)性能分析

1)Tn

折半查找仅在查找时做了优化,但是在交换时O(n),总共需要n-1轮,so Tn  = O(n^2)

best:O(n^2)

worst:O(n^2)

avg:O(n^2)

2)Sn

O(1)

3)稳定性

查找时可能会发生交换,so不稳定

4)适用性

二分查找适用于顺序表且有序的,so链式表不行

3.希尔排序(ShellSort)(手算)

(1)背景

因为直接插入排序在有序or大致有序时效率较高,so在希尔提出将数组分成若干了子数组,那么在各个子数组中就是大致有序的

(2)思路

依次减少增量d,以距离为d的子ele作为一个子列,在子列中进行插入排序

d一般为数组长度依次/2 ,直到1为止

(3)实现

颜色分组

(4)性能分析

1)Tn

当d直接为1时,直接退化为直接插入排序,O(n^2)

当d在某些范围内,可达到O(n^1.3)

2)Sn

O(1)

3)稳定性

分成不同组的时候可能会交换相对位置,so不稳定,上例可知

4)适用性

因为要求随机存取,so只适用于顺序表

三、交换排序

1.冒泡排序(BubbleSort)

(1)思路

从后->前 or 从前->后,两两交换,大的往后走,一轮可以定一个,so之后就不用比较了,if在一轮中无ele交换,则说明已经有序,排序停止(*考趟数)

(2)实现

void BubbleSort(SSTable A,int n){
    //排序往往用两层循环,优先写内层,之后写外层
    //内层循环控制比较交换,外层控制有序数的个数
    int i,j;
    bool flag;//添加哨兵,才能实现最高时间复杂度O(n)
    for (int i=0;i<n-1;i++) {
        flag = false;
        //一轮比较下来没有交换的,说明已经有序了
        //每一轮就定一个,因此j比较次数是动的,用外层控制
        for (int j = n-1; j > i ; j--) {
            if(A.elem[j] < A.elem[j-1]){
                swap(A.elem[j],A.elem[j-1]);
                flag = true;
            }
        }
        if(false == flag ){//未进行交换
            return;
        }
    }
}
void swap(int &a,int &b){
    int tmp;
    tmp = a;
    a = b;
    b =tmp;
}

(3)性能分析

1)Tn

best:都是有序的,只用进行n-1轮比较,O(n)

worst:都是逆序,比较n-1轮,每轮交换i-1次,O(n^2)

avg:O(n^2)

2)Sn

O(1)

3)稳定性

当相同时设置不交换,so稳定

4)适用性

顺序表和链表均可

2.快速排序(QuickSort)

(1)思路

以第一个ele作为枢轴,依次与后面eles进行比较,大的放右面,小的放左面,这样每一轮就可以确定一个ele,然后左右分治递归即可

(2)实现

void QuickSort(ElemType A[],int low,int high){
    //low最左边元素,high最右边元素
    if(low <high) {//至少2个元素
        //partition->O(n)分成两半while->O(logn)
        //O(n)O(logn)
        //最差O(n^2)
        // reason:有序的数组,递归n次,递归一次O(n)
        int pivot_pos = partition(A, low, high);//找定哪个位置
        //pivot:n.支点,中心点
        //之后再递归前一半
        QuickSort(A, low, pivot_pos - 1);
        //后一半
        QuickSort(A, pivot_pos + 1, high);
    }
}
//定一个元素位置操作
//挖坑法
int partition(ElemType A[],int low,int high){
    ElemType pivot=A[low];//存分割值,最左边的元素
    //∵用pivot存储了定位的值,so可以直接覆盖,不交换了,effective
    //high 大 high--, 小 A[low] = A[high] low++ high--
    //间隔着换,low换完high换,high换完low换
    while(low<high){
        while(low<high && A[high]>=pivot){//找到比分割值小的元素
            high--;
        }
        A[low]=A[high];

        while(low<high && A[low] <= pivot){
            low++;
        }
        A[high] = A[low];
    }
    //找到位置了
    A[low] =pivot;

    return low;
}

(3)性能分析

1)Tn

主要取决于递归的次数,相当于二叉树

best:每次枢轴可以将左右平分 O(nlogn)

worst:有序or大致有序 O(n^2)

avg: 整体效率接近 O(nlogn)

if每次次枢轴可以将左右平分,那么就可以提高效率

way1:取头部中间尾部,比较大小,取中间

way2:随机取

2)Sn

递归栈的层数(二叉树的高度)O(logn)

3)稳定性

不稳定

4)适用性

顺序表链表均可,但是顺序表效率高

(4)一次划分 vs 一趟排序

408要求一次划分只能定一个ele,一趟排序可以定多个,相当于QuickSort的一层

四、选择排序

每次选择一个定下来最终位置

1.简单选择排序

(1)思想

遍历arr,选择min与第一个交换

(2)实现

void SelectionSort(int A[], int n) {
    for (int i = 0; i < n - 1; ++i) {
        int min = i;
        for (int j = i + 1; j < n; ++j) {
            if (A[j] < A[min]) min = j;
        }
        if (min != i) swap(A[i], A[min]);
    }
}

2.堆排序(以大根堆为eg)

(1)思想

初始化堆,建立大根堆

选择根节点,之后重复建堆

(2)实现

void HeapAdjust(int A[],int k,int len){
   A[0] = A[k];
    for (int i = 2*k; i <=len ; i*=2) {
        if(i<len&&A[i]<A[i+1]) i++;
        if(A[0] >= A[i]) break;
        else{
            A[k] = A[i];
            k=i;
        }
    }
    A[k] = A[0];
}

void BuildMaxHeap(int A[],int len){
    for (int i = len/2; i >0 ; i--) {
        HeapAdjust(A,i,len);
    }    
}

void HeapSort(int A[],int len){
    BuildMaxHeap(A,len);
    for (int i = len; i >1 ; --i) {
        swap(A[i],A[1]);
        HeapAdjust(A,i,i-1);
    }
}

五、归并排序(MergeSort)

1.基本概念

归并:将k个子数列归并成一个子数列,同时变为升序

2.思路

归并排序一般是二路归并,即把两个arr变成一个arr

so先把A[]分成一个个子arr,然后依次进行merge即可

attn:合并时每组需要排序,此处省略

3.实现

int *B = (int *) malloc(7 * sizeof(int));//辅助空间

void Merge(int A[], int low, int mid, int high) {
    int i, j, k;
    for (k = low; k <= high; ++k) {
        B[k] = A[k];//先把A中复制到B中
    }
    for (i = low, j = mid + 1, k = i; i <= mid && j <= high; ++k) {
        if (B[i] <= B[j])//排序,升序
            A[k] = B[i++];
        else
            A[k] = B[j++];
    }
    //有一个子arr为null
    while (i <= mid) A[k++] = B[i++];
    while (j <= high) A[k++] = B[j++];
}

//总过程
void MergeSort(int A[], int low, int high) {
    if (low < high) {
        int mid = (low + high) / 2;
        //先递归只剩1个ele
        MergeSort(A, low, mid);
        MergeSort(A, mid + 1, high);
        //依次merge
        Merge(A, low, mid, high);
    }
}

4.性能分析

(1)Tn

MergeSort可以视作倒的二叉树,so高度为logn

每次归并Tn= O(n)

so O(nlogn)

(2)Sn

一个辅助空间 O(n)

(3)稳定性

稳定,当子arr中有相同ele,要求先复制前面arr中ele

六、基数排序(RadixSort)

1.手算

eg:520、211、438、888、007、111、985、666、996、233、168

第一次分配收集

第二次分配收集

第三次分配收集

2.思路

(1)初始化

先确定分成几类(几趟),即d,之后根据每一类的最大最小值,确定r(辅助队列的范围)

(2)分配

根据每一位上数字大小分配到第几个队列上

(3)收集

从大到小排序

3.效率分析

(1)Tn

d趟O(d),一趟O(r+n) so O(d(n+r))

(2)Sn

辅助队列O(r)

(3)稳定性

稳定

4.application

三个条件同时满足

(1)可以分成的类d较少

(2)每个位置的取值r范围较小

(3)总数n较大

eg:10000个学生的年龄排序,分成年份、月份、日 

5个人的身份证号排序  x

10000个人的身份证号排序 √   d = 18 r = 10

七、外部排序

1.准备

因为数据量过大,so不能将所有数据放入内存,只能放在磁盘中

so排序前需要将data读入内存,排序之后写入磁盘

2.思路

读写+归并排序

step1:构建初始归并段,根据输入缓冲区的大小确定

step2:读入内存,进行归并排序

step3:写入磁盘

3.k路归并

eg:

step1:因为输入缓冲区只有两块,so只能放入前两块

第一轮之后:(仅以前两块为例)

再分归并段:(2块1段)

ATTn:当归并段第一块读空,需要先补充第二块,再归并排序

reason:防止乱序

第二轮之后:

以此类推最后全部有序

4.Tn

总花费时间= 读写时间+归并时间+排序时间

ATTn:读写占主要时间

5.优化

(1)多路归并

同时将多块数据读入内存,从而可以减少读写的次数

(2)败者树

1)background

if仅使用多路平衡归并,则排序占的时间增多,败者树用来提高排序的效率

2)说明

当第一次构建败者树的时候需要比较n-1次,之后比较仅需log2n up次(树高)

因为在排序之前需要初始归并段,用bn表示,so叶节点表示的不是数的大小,而是第几个归并段

3)构建

小的晋级,大的留下

解释:圆形结点表示是哪个归并段的data,线上的是晋级的,so下次比较仅需沿着path依次比较即可

(3)置换-选择排序(手算)

1)background

选择合适归并段的个数可以提高效率,置换选择排序是用来确定几个归并段

置换表示每次确定ele之后换新的,选择表示选择min

2)思想

输入缓冲区的大小是固定的,so将待排序data依次放入输入缓冲区中,依次选出最小的ele放入归并段,并记录该归并段最大数,当缓冲区中所有ele都小于该归并段的最大数,停止,之后进行下一个归并段的构建

3)构建

eg:4 6 9 7 13 11 16 14 10 22 30 2 3 19 20 17 1 23 5 36 12 18 21 39 输入缓冲区容量为3

此时输入缓冲区中的所有data都<Max,so第一个归并段确定

此时第二个归并段确定

最后确定

(4)最佳归并树

1)background

当经过置换-选择排序之后,归并段的长度不同,那么如何选择哪两个归并段进行归并排序的效率最高呢?即使用最佳归并树,本质就是k叉的哈夫曼树

2)计算

I/O次数=2*WPL

3)构建(3路平衡归并树)

eg: 2 3 6 9 12 17 24 18

WPL = (9+12+17+18+24)*2+(2+3+6)*3=193

if按正常步骤去构建,可知该树并非严格3叉树

so 需要添加一个为0的虚结点

WPL = 24*1+(6+9+12+17+18)*2+(2+3)*3=163

4)确定虚结点个数

已知待排序结点个数,严格k叉树度为k的结点数nk,度为0的结点数n0,总结点数n

so n = nk+n0

n = k*nk+1

nk = (n0-1) / (k-1)

so 要求可以整除

即   n0-1 % k-1 =0

if == 0 不需要虚节点

else if n0-1 % k-1 = u ,则需要 k-1-u个

reason:多了u个,so补到k-1个

  • 30
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

blue_blooded

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值