数据结构之排序

前言

本文是我大二学习数据结构时,根据自己的学习和理解而写的总结(主要是面临期末给自己总结一下,加深理解)


什么是排序?!

  1. 就是把所有元素按照递增或递减有序排列。
  2. 内排序?外排序?排序过程中放在内存中处理,若不涉及内外存交换就是内排序;反之外排序。
  3. 稳定性?不稳定性?排序前后具有相同关键字的次序不变,这种排序方法就是稳定的;反之不稳定的。
排序中的一些有关定义(我本人是真的不喜欢记这些什么东西,感觉每个人有每个人的理解,没必要规定死,但是为了统一所以才有的规定吧)
  1. 元素序列:直白的说就是,一个数组a[10],a[0],a[1]...中0,1,2...就是所谓的元素序列。
  2. 关键字:每一个元素序列存放的值(a[0]=k,k就是关键字)
  3. 正序和反序:待排序元素的关键字顺序和排序后的顺序一样就是正序。待排序的元素关键字和排序后的顺序正好相反就是反序。
  4. 有序区和无序区:已经排好顺序的区域就是有序区,还未排序的区域就是无序区。

一、内排序


  • 直接插入排序


    直接插入排序(Straight Insertion Sort)的基本原理是将待排序的元素分为有序和无序两个部分,开始时有序表中只包含1个元素,无序表中包含有n-1个元素。排序过程中每次从无序表中取出第一个元素,将它插入到有序表中的适当位置,使之成为新的有序表,重复n-1次可完成排序过程。


    网上随便搜的一个定义

    但是我自己是不喜欢记定义的

    按我自己的理解就是:

    例如一个长度为5的数组[3,7,1,-1,4]递增排序

    首先把3取出作为初始元素(也就是第一个元素)

    接着从7开始依次和7前面的数比较

    第一次插入后的顺序3,7

    第二次插入后的顺序1,3,7

    依次类推就是:-1,1,3,7

    -1,1,3,4,7

    代码实现(java)

    public void InserSort(){
            RecType tmp; //RecType 一个存放元素的类
            int j;
            for(int i = 1; i < n; i ++){
                if(R[i].key < R[i-1].key){
                    tmp = R[i];
                    j = i - 1;
                    do{
                        R[j + 1] = R[j];//元素依次后移
                        j --;
                    }while(j >=0 && R[j].key < R[j-1].key);
                    R[j + 1] = tmp;//后移截止后,把一开始的元素插入
                }
            }
        }

    算法分析:

    1>最好情况:初始元素序列为正序,元素不用移动只用比较,则有最小值的比较次数和移动次数,即C = \sum_{i=1}^{n-1}1= n - 1 = O(n), M = 0。

    2>最坏情况:初始元素序列为反序,C = n(n-1)/2 = O(n^{2}), M = (n-1)(n+4)/2 =  O(n^{2})

    3>平均情况:C = n(n-1)/4 =O(n^{2}), M = \sum_{i=1}^{n-1}(i/2 +2) = O(n^{2})

    4>空间复杂度为O(1)

    5>是一种稳定的排序算法

  • 折半插入排序


折半插入排序(Binary Insertion Sort)是一种插入排序算法,它的基本原理是将折半查找方法与直接插入排序方法相结合。在每一次插入新元素时,它利用折半查找方法找到其待插入的位置。

具体来说,折半插入排序的运行过程如下:

  1. 将待排序序列的第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
  2. 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。在查找元素的适当位置时,采用了折半查找方法。如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。

过程如下图:

直白点就是,和直接插入排序类似,但是在插入过程中,直接插入是依次往前一一比较,但是折半插入是利用二分的方法进行比较。

代码实现(java):

public void BinInsertSort(){
        int l, r, mid;
        RecType tmp;
        for(int i = 1; i < n; i ++){
            if(R[i].key < R[i-1].key){
                tmp = R[i];
                l = 0; r = i - 1;
                while(l <= r){//利用二分查找合适插入点
                    mid = l + r >> 1;//(l + r) / 2;
                    if(R[mid].key > tmp.key) r = mid - 1;
                    else l = mid + 1;
                }
                for(int j = i - 1; j >= r + 1; j --) //后移元素
                    R[j+1] = R[j];
                R[r + 1] = tmp;//插入元素
            }
        }
    }

算法性能:

1>平均关键字比较次数:\log_{2}(i+1) - 1

2>平均移动次数为:i / 2 + 2

3>平均复杂度:O(n^{2})

4>空间复杂度为O(1)

5>一种稳定的排序算法

  • 希尔排序


希尔排序原理:

将待排序文件分成若干组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后取gap/2的数进行排序,重复上述分组和排序的工作。当到达gap=1时,所有记录在统一组内排好序。

算法实现(java):

public void ShellSort(){
        RecType tmp;
        int d = n / 2;//增量初始化
        while(d > 0){
            for(int i = d; i < n; i ++){//采用直接插入进行排序
                int j = i - d;
                tmp = R[i];
                while(j >=0 && R[j].key > tmp.key){
                    R[j + d] = R[j];
                    j -= d;
                }
                R[j + d] = tmp;
            }
            d /= 2;//递减增量
        }
    }

算法性能:

一般认为平均复杂度为:O(n^{1.58})

空间复杂度O(1)

不稳定 排序算法

  • 冒泡排序


冒泡排序的基本原理是:对于未排序的序列,从第一个元素开始,比较相邻的两个元素,如果顺序错误就交换位置。重复这个过程,直到没有需要交换的元素为止。

具体来说,冒泡排序的步骤如下:

  1. 比较相邻的元素。如果第一个比第二个大(或小),就交换他们两个。
  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次针对剩下的元素重复上面的步骤,直到没有任何一对数字需要比较。

简单的就是2个for循环然后比较相邻元素,直到没有元素需要交换为止。

代码实现(java):

public void BubbleSort(){
        boolean exchange;
        for(int i = 0; i < n - 1; i ++){
            exchange = false;
            for(int j = n - 1; j > i; j --){
                if(R[j].key < R[j - 1].key){
                    RecType tmp = R[j];
                    R[j] = R[j - 1];
                    R[j - 1] = tmp;
                    exchange = true;
                }
            }
            if(!exchange) break;
        }
    }

算法性能:

1>最好情况:(初始序列为正序)C = \sum_{i=0}^{n-2}1 = n - 1 = O(n),M = 0

2>最坏情况:(初始序列为反序)C = n(n-1) / 2= O(n^{2}), M = 3n(n-1) / 2 = O(n^{2})

3>平均时间复杂度:O(n^{2}),空间复杂度O(1),是一种稳定的排序算法

  • 快速排序


    原理:

    快速排序采用的是分治思想,即在一个无序的序列中选取一个任意的基准元素pivot,利用pivot将待排序的序列分成两部分,前面部分元素均小于或等于基准元素,后面部分均大于或等于基准元素,然后采用递归的方法分别对前后两部分重复上述操作,直到将无序序列排列成有序序列。

    说人话就是:上面的好像已经够人话了

    代码实现(java):

    public void QuickSort()
        {
            QuickSort1(0,n-1);
        }
        public void QuickSort1(int s,int t){
            if(s < t){
                int i = Partition(s,t);
                QuickSort1(s,i-1);
                QuickSort1(i+1,t);
            }
        }
        public int Partition(int s, int t)
        {
            RecType tmp;
            RecType base = R[s];
            int i = s, j = t;
            while(i < j){
                while(i < j && R[j].key >= base.key) j--;
                while(i < j && R[i].key <= base.key) i ++;
                if(i < j){
                    tmp = R[i];
                    R[i] = R[j];
                    R[j] = tmp;
                }
            }
            tmp = R[s];
            R[s] = R[i];
            R[i] = tmp;
            return i;
        }

    算法性能:

最好情况:初始数据随机分布且每次划分都是2个长度相同的子表,每层的时间为O(n)

                   递归树高度为\log_{2}(n+1) 最终的时间复杂度为O(n\log_{2}n)

最坏情况:初始数据为正序或反序,递归树高度为O(n),每层的时间为O(n),最终时间复杂度为O(N^{2})

平均:时间复杂度:O(n\log_{2}n),空间复杂度:O(\log_{2}n)

  • 简单选择排序


原理:第一趟,从 n 个元素中找出关键字最小的元素与第一个元素交换
第二趟,在从第二个元素开始的 n-1 个元素中再选出关键字最小的元素与第二个元素交换

第 k 趟,则从第 k 个元素开始的 n-k+1 个元素中选出关键字最小的元素与第 k 个元素交换

算法实现(java):

public void SelectSort(){
        for(int i = 0; i < n; i ++){
            RecType tmp;
            int k = i;
            for(int j = i + 1; j < n; j ++)
                if(R[j].key < R[i].key) k = j;
            if(k!=i){
                tmp = R[k];
                R[k] = R[i];
                R[i] = tmp;
            }
        }
    }

算法性能:

1>比较次数C = n(n-1) / 2 = O(n^{2}),无论数据如何分布比较次数是一样的

2>空间复杂度 O(1)

3>移动次数:最好时(正序):0,最坏(反序):3(n-1),
4>最好、最坏、平均的时间复杂度都是O(n^{2})

  • 堆排序


    堆排序(Heap Sort)的原理是利用堆这种数据结构所设计的一种排序算法。堆排序可以分为两个主要步骤:建堆和排序。建堆的目的是将原始数组构建成一个大顶堆(或小顶堆),然后将堆顶元素(最大值或最小值)与堆尾元素互换,之后将剩余元素重新调整为大顶堆(或小顶堆),以此类推,直到整个数组有序。

    堆的性质:小根堆:Ki <= K2i  且 Ki <= K2i+1

                     大根堆:Ki >= K2i  且 Ki >= K2i+1

    代码实现(java):

    public void sift(int low, int high){//筛选算法
            int i = low, j = i * 2;//R[j]是R[i]的左孩子
            RecType tmp = R[i];//tmp用于保存根节点
            while(j <= high){
                if(j < high && R[j].key < R[j+1].key)
                    j ++;//若右孩子大,j指向右孩子
                if(tmp.key < R[j].key)//tmp的孩子 较大,则R[j]调整到双亲位置
                {
                    R[i] = R[j];
                    i = j;
                    j = 2 * i;
                }
                else break;//孩子小,筛选结束
            }
            R[i] = tmp;//原根节点放入最终位置
        }
    
        public void HeapSort()//R[0..n-1]递增排序
        {
            for (int i = n / 2; i >= 0; i--)//建立初始堆
                sift(i,n - 1);
            for (int i = n - 1; i >= 1; i--) {
                RecType tmp = R[i];
                R[i] = R[0];
                R[0] = tmp;
                sift(0,i - 1);
            }
        }

    算法性能:

总比较次数:C <= 2n\log_{2}n+n

元素移动次数:M=n\log_{2}n + O(n)

最坏、最好、平均时间复杂度:O(n\log_{2}n

空间复杂度O(1)

一种不稳定算法

  • 二路归并排序


    归并排序的原理:

    将两个或两个以上的有序表合并成一个新的有序表。具体来说,归并排序使用了分治策略,将待排序的数组分成若干个子数组,每个子数组都是有序的,然后再将这些有序的子数组合并成一个完整的、有序的数组。合并的过程中需要进行比较和交换操作,直到整个数组有序。

    例如,9,8,7,6,5,4,3,2,1,0

    代码实现(java):

    public void Merge(int low, int mid, int high){
            RecType[] R1 = new RecType[high - low + 1];
            int i = low, j = mid + 1, k = 0;
            while(i <= mid && j <= high){
                if(R[i].key <= R[j].key){
                    R1[k] = R[j];
                    i ++;
                    k ++;
                }
                else {
                    R1[k] = R[j];
                    j ++;
                    k ++;
                }
            }
            while(i <= mid) {
                R1[k] = R[i];
                i ++;
                k ++;
            }
            while(j <= high) {
                R1[k] = R[j];
                j ++;
                k ++;
            }
            for (k = 0, i = low; i<=high ; k++,i++) R[i] = R1[k];
        }
        public void MergePass(int len){
            int i;
            for (i = 0; i + 2 * len - 1 < n; i = i + 2 * len) //归并len长的相邻子表
                Merge(i, i + len - 1, i + 2 * len - 1);
            if(i + len < n) Merge(i, i + len - 1, n - 1);//归并余下的表
        }

    算法性能:

最好、最好、平均的时间复杂度都是O(n\log_{2}n)

空间复杂度O(n)

        自顶向下和自底向上:

自顶向下:是在初始序列基础上先分解在合并。

自底向上:是在初始序列基础上直接进行合并

  • 基数排序


    基数排序的原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。具体来说,它从最低位开始,对每一位数字进行比较和交换,使得每一位数字都按照从小到大的顺序排列。然后按照同样的方法对下一位数字进行排序,直到最高位。

    举例来说就是:

    代码实现(java):

public class RadixSortClass {
    LinkNode head = null;

    public void CreateList(int[] a){
        LinkNode s,t;
        head = new LinkNode(a[0]);
        t = head;
        for(int i = 1; i < a.length; i ++){
            s = new LinkNode(a[i]);
            t.next = s;
            t = s;
        }
        t.next = null;
    }

    private int geti(int key,int r,int i){//基数为r时,关键字key的第i位
        int k = 0;
        for(int j = 0; j <= i;j ++){
            k = key % r;
            key /= r;
        }
        return k;
    }

    public void RadixSort(int d, int r){//d是最大位数,r是进制数
        LinkNode p,t = null;
        LinkNode[] h = new LinkNode[r];//队头链表
        LinkNode[] tail = new LinkNode[r];//队尾链表
        for (int i = 0; i < d; i++) {//低位->高位的循环
            for (int j = 0; j < r; j++) h[j] = tail[j] = null;
            p = head;
            while (p!=null){//分配过程
                int k = geti(p.key,r,i);
                if(h[k] == null){
                    h[k] = p;
                    tail[k] = p;
                }
                else {
                    tail[k] = p;
                    tail[k].next = p;
                }
                p = p.next;
            }
            head = null;//重新收集过程
            for (int j = 0; j < r; j++)
                if(h[j]!=null){
                    if(head == null) {
                        head = h[j];
                        t = tail[j];
                    }
                    else {
                        t.next = h[j];
                        t = tail[j];
                    }
                }
            t.next = null;
        }
    }
    public void Disp(){
        LinkNode p = head;
        while(p!=null){
            System.out.print(p.key + " ");
            p = p.next;
        }
    }
}
class LinkNode{
    int key;
    String e;
    LinkNode next;

    public LinkNode(int key) {
        this.key = key;
        this.next = null;
    }
}


算法性能:

时间复杂度O(d(r+n))

空间复杂度O(1)

稳定的排序算法

稳定排序算法可以简记为:鸡(基数)毛(冒泡)插(插入)龟(归并)壳(网上无意中看见的,挺有意思的)

  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值