数据结构内排序之惨死攻略(一)

目录

1 基本定义

2 插入排序

2.1 直接插入排序(Straight Insertion Sort)

2.1.1 栗子

2.1.2 代码实现

2.1.3 算法分析

2.2 Shell排序

2.2.1 栗子——增量每次除以2递减

2.2.2 代码实现

2.2.3 算法分析

2.2.4 增量选择

3 选择排序

3.1 直接选择排序

3.1.1 栗子

3.1.2 代码实现

3.1.3 算法分析

3.2 堆排序

3.2.1 栗子

3.2.2代码实现

3.2.3 算法分析

4 交换排序

4.1 冒泡排序

4.1.1 栗子

4.1.2 代码实现

4.1.3 算法实现

4.2 快速排序

4.2.1 轴值的选择(核心)

4.2.2 栗子

4.2.3 代码实现

4.2.4 算法分析


1 基本定义

内排序:整个排序过程在内存中完成

排序的稳定性

  • 存在多个具有相同排序码的记录
  • 排序后这些记录的相对次序保持不变

排序算法的时间代价:记录的比较和移动次数

2 插入排序

《插入排序之舞》

2.1 直接插入排序(Straight Insertion Sort)

发扑克牌的时候我喜欢一边摸牌,一边理牌。这个过程就类似于直接插入排序。

基本操作:将一个记录插入到已经排好序的有序表中,从而得到一个新的,记录数+1的有序表。

2.1.1 栗子

本来有牌45

抽到了一张34,放45的前面

抽到了一张78,放45的后面

抽到了一张12,依次跟78,45,34比较,最后放在45的前面

其他同理

2.1.2 代码实现

template<class Record>
void ImprovedInsertSort(Record Array[], int n){//Array为待排数组,n为数组长度
    Record TempRecord;    //临时变量
    //将待排元素一个个取出,存到临时变量里
    for(int i=1; i<n; i++){
        TempRecord = Array[i];
        //记录i往前与序列中的元素比较
        int j = i - 1;
        //将大于等于记录i的记录后移(j=-1时,即原序列无元素,记录i为第一个元素)
        while(j >= 0 && (TempTecord < Array[j])){
            Array[j+1] = Array[j];
            j = j - 1;
        }
        //此时j后面就是记录i的位置
        Array[j+1] = TempRecord
    }
}

2.1.3 算法分析

2.2 Shell排序

《希尔排序之舞》

基于直接插入排序的两个性质

  1. 序列本身有序的情况下,时间代价为O(n)
  2. 对于短排序,直接插入排序比较有效

Shell排序算法思想

  1. 将序列转化为若干小序列,在这些小序列内进行插入排序
  2. 逐渐壮大小序列的规模,减少序列个数,使待排序序列逐渐处于更有序的状态
  3. 最后对全体记录进行一次直接插入排序

2.2.1 栗子——增量每次除以2递减

选择增量为4的两个元素为一组(4 = 待排序列总数 / 2)

采用直接插入排序

选择增量为2的4个元素为一组,采用直接插入排序

最后一轮,增量为1,对全部记录进行一次直接插入排序

2.2.2 代码实现

template<class Record>
void ShellSort(Record Array[], int n){//Array[]为待排序数组,n为数组长度
    int i, delta;
    //增量delta每次除以2递减,直到delta=1
    for(delta = n/2; delta>0; delta /=2)
        for(i = 0; i<delta; i++)
            //分别对delta个子序列进行插入排序
            //&传Array[i]的地址,数组总长度为n-i
            //i=0,数组长为n,delta=n/2
            ModInsSort(&Array[i],n-i,delta);
            //如果增量序列不能保证最后一个delta间隔为1
            //可以安排下面这个扫尾性质的插入排序
            //ModInsSort(Array, n, 1)
}

针对增量而修改的插入排序算法

template<class Record>
void ModInsSort(Record Array[], int n, int delta){
    int i, j;
    //对子序列中第i个记录,寻找合适的插入位置
    //以下标为delta的记录开始处理,其前面的元素下标为0
    for(i = delta; i < n; i += delta)
        //j以delta为步长向前寻找逆序对进行调整
        for(j = i; j >= delta; j -= delta){
            if(Array[j] < Array[j-delta])//逆序对
                swap(Array,j,j-delta);//交换
            else break;
        }
}

2.2.3 算法分析

2.2.4 增量选择

(1)增量不互质

选取的增量之间不互质,导致重复排序。如

45与34'第一轮在同一子序列里

第二轮还在同一子序列里

(2)增量互质

3 选择排序

《选择排序之舞》

3.1 直接选择排序

依次选出剩下的未排序的记录中的最小记录。

过程类似于打擂台。

3.1.1 栗子

第一回合

第一个站在擂台上的是45,34去跟45打,34赢了。

78去跟34打,78输了,目前站在擂台的还是34。

12去跟34打,精彩,34输了,12领先。

34'去跟12打,输了。32去跟12打,输了。29去跟12打,输了。64去跟12打,还是输。

第一回合,获胜的是12!其他同理。

3.1.2 代码实现

template<class Record>
void SelectSort(Record Array[], int n){
    //依次选出第i小的记录,即剩余记录中最小的那个
    for(int i=0; i<n-1; i++){
        //首先假设记录i就是最小的
        int smallest = i;
        //开始向后扫描所有剩余记录
        for(int j=i+1; j<n; j++)
            //如果发现更小的记录,记录它的位置
            if(Array[j] < Array[smallest])
                smallest = i;
        swap(Array,i,smallest);//将smallest赋值给Array[i] 
    }
}

3.1.3 算法分析

3.2 堆排序

基于最大堆来实现。而堆的本质是一棵用数组表示的完全二叉树。

3.2.1 栗子

(1)建立最大堆

将已经存在的N个元素按最大堆的要求存放在一个一维数组中。

(2)找到最大值就将其删除。即将堆里的最后元素与堆顶元素交换

(3)最大值78出堆,堆的规模-1。剩余的元素从根开始调整。

3.2.2代码实现

template<class Record>
void sort(Record Array[], int n){
    int i;
    //建堆
    MaxHeap<Record> max_heap = MaxHeap<Record>(Array,n,n);
    //算法操作n-1次,最小元素不需要出堆
    for(i=0; i<n-1; i++)
        //依次找出剩余记录中的最大记录,即堆顶
        max_heap.RemoveMax();
}

3.2.3 算法分析

此外可以改写堆排序算法,发现逆序对直接交换。

4 交换排序

4.1 冒泡排序

不断比较相邻的记录,不满足排序要求的就交换相邻记录,直到所有记录都已经排好序。

《冒泡排序之舞》

4.1.1 栗子

从最后开始。发现逆序对就交换。

发现逆序对32 29

交换位置

发现逆序对34’ 29

交换位置

......第一轮比较结束后,选出了最小元素12

直接选择排序中没有这么多次的交换,站在擂台上的数不一定是最小数。

4.1.2 代码实现

template<class Record>
void BubbleSort(Record Array[], int n){
    bool NoSwap;            //用来标识是否发生了交换
    int i, j;
    for(i = 0; i<n-1; i++){//比较n-1轮
        NoSwap = true;      //标识初始为true  
        for(j = n-1; j>i;j--){
            if(Array[j] < Array[j-1]){//判断是否为逆序对
                swap(Array,j,j-1);//交换逆序对
                NoSwap = false;        //发生了交换,标志为false
            }
        }
        if(NoSwap)        //这一轮的元素都是排好序的,没发生交换,直接退出冒泡排序
            return;       
    }
}

4.1.3 算法实现

4.2 快速排序

《快速排序(Quicksort)学习笔记》

  • 选择轴值pivot(核心)
  • 将序列划分成两个子序列L和R,使得L中所有记录都小于或等于pivot,R中记录都大于pivot
  • 对子序列L和R递归进行快速排序
  • 基于分治策略

4.2.1 轴值的选择(核心)

尽可能使得L,R长度相等

  • 选择首元素或尾元素作为轴值(完全正序或完全逆序的输入就会比较糟糕)
  • 随机选择
  • 选择平均值(代价)

4.2.2 栗子

一次的分割过程

选择32作为轴值

然后把最右端的元素64填充进去,然后最右边的位置就空出来了

接着从左边开始看,将左边的元素25与轴值32做比较。

25<32,不是逆序元素,i继续往右走指向34,34>32,34填充到最右边的位置。此时左边空出了一个位置。

此时左边空出了一个位置。移动右边的j,从右边找一个逆序元素29填入左边的空位。

此时右边空出了一个位置。从左边找一个逆序元素45填入右边的空位。

风水轮流转,左边有空位了。继续移动右边的j,从右边找一个逆序元素12填入左边的空位

最终

具体过程描述:

  • 选择轴值并存储轴值(上述例子取中间记录作为轴值)
  • 最后一个元素放到轴值位置
  • 初始化下标 i, j,分别指向头尾
  • 递增直到遇到比轴值大的元素,将此元素覆盖到 j 的位置;j 递减直到遇到比轴值小的元素,将此元素覆盖到 i 的位置
  • 重复上一步直到 i==j,将轴值放到 i 的位置,完毕

4.2.3 代码实现

template<class Record>
void QuickSort(Record Array[], int left, int right){
    //Array[]为待排序数组,left,right分别为数组两端
    if(right <= left)    //如果只有0个或1个记录,就不需要排序
        return;
    int pivot = SelectPivot(left,right);    //选择轴值
    swap(Array,pivot,right);                //轴值放到数组末端
    pivot = Partition(Array,left,right);    //分割后轴值正确
    QuickSort(Array,left,pivot-1);          //左子序列递归快排
    QuickSort(Array,pivot+1,right);         //右子序列递归快排
}
int SelectPivot(int left,int right){
    //选择轴值,参数left,right分别表示序列的左右端下标
    return(left+right)/2; //选中间记录作为轴值
}

分割函数

template<class Record>
int Partition(Record Array[],int left,int right){
    //分割函数,分割后轴值已到达正确位置
    int l = left;      //l为左指针
    int r = right;     //r为右指针
    Record TempRecord = Array[r];//保存轴值
    while(l != r){//l,r不断向中间移动,直到相遇
        //l指针向右移动,直到找到一个大于轴值的记录
        while(Array[l] <= TempRecord && r>l)
            l++;
        if(l<r)//未相遇,将逆置元素换到右边空位
            Array[r] = Array[l];
            r--;  //r指针向左移动一步
        //r指针向左移动,直到找到一个小于轴值的记录
        while(Array[r] >= TempRecord && r>l){
            r--;
        if(l < r){       //未相遇,将逆置元素换到左空位
            Array[l] = Array[r];
            l++;         //l指针向右移动一步
        }
    }//end while
    Array[l] = TempRecord;//将轴值回填到分界位置上
    return l;  //返回分界位置l
}

4.2.4 算法分析

你们以为这就结束了?天真!

下面转至《数据结构内排序之惨死攻略(二)》


学习自:

张铭《数据结构》

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章