数据结构-排序

来源:互联网 发布:360电脑管家mac版 编辑:程序博客网 时间:2024/06/06 01:40

1.基本概念

排序:所谓排序,就是将原本无序的序列重新排列成有序的序列。

稳定性:稳定性指的是需要排序的序列中,有2个或者多个相同的元素项,在排序前和排序后,他们之间的相对位置并没有发生变化。如果没有发生变化就是稳定的;如果发生变化就是不稳定的。

排序算法的分类

  1. 插入类排序
    对一个已经有序的序列中,插入一个新的记录。就好比军队排队,已经排好了一个纵队,现在来了一个新的人加入到对于,这是军官喊:“新来的,迅速找到你的位置,入队。”于是新来的“插入”到了这个队伍中合适的位置。属于这种插入排序的有“直接插入排序”、“折半插入排序”、“希尔排序”。

  2. 交换类排序
    交换类排序的核心是“交换”,即每一趟排序,都通过一系列的“交换” 动作,让一个记录排到它最终的位置。属于这种交换类排序有冒泡排序快速排序

  3. 选择类排序
    选择类排序的核心是“选择”,即通过每一趟排序都选出一个最小(最大)的元素项,然后将它与序列中的第一个(或者最后一个)进行交换,这样最小(最大)的元素项就已经就位了。属于选择类排序的有简单选择排序堆排序

  4. 归并类排序
    归并类排序是将2个或者2个以上的有序序列合并成一个新的有序序列。属于归并类排序的有二路归并排序

  5. 基数类排序
    基数类的排序是最特别的一类,跟之前的排序思想完全不同(之前的排序是进行“比较”、“移动”这2个操作)。基数类排序是基于多关键字排序的思想,把一个逻辑关键字拆分成多个关键字。例如对于一副已经去除了大小王的52张扑克牌中,按照基数排序类的思想,可以先按照花色排(黑桃、红桃、方块、梅花),这样就分成了4堆,然后每一堆在按照A到K的顺序排,这样这副牌就最终有序了。一个逻辑关键字(如黑桃8)被拆分为多个关键字(黑桃和8)就是这个意思了。

2.插入类排序

2.1 直接插入排序:

① 思想:最基本的插入排序,将第i个插入到前i-1个中的适当位置。

② 时间复杂度:T(n) = O(n²)。

③ 空间复杂度:S(n) = O(1)。

④ 稳定性:稳定排序。

⑤ 程序:

    public static void InsertSort(int[] array,int length){        int temp;   //保存插入值        int j;      //记录与插入值比较的索引        for(int i = 1;i < length;i++){            temp = array[i];            j = i-1;            while(j >= 0 && array[j] > temp ){                array[j+1] = array[j];                j = j - 1;            }            array[j+1] = temp;        }    }

2.2折半插入排序:

① 思想:因为是已经确定了前部分是有序序列,所以在查找插入位置的时候可以用折半查找的方法进行查找,提高效率。

② 时间复杂度:比较时的时间减为O(n㏒n),但是移动元素的时间耗费未变,所以总是得时间复杂度还是O(n²)。

③ 空间复杂度:S(n) = O(1)。

④ 稳定性:稳定排序。

⑤ 程序:

    public static void BinSort(int[] array,int length){        int num = length -1;//最后一个元素为待插入的元素        int x = array[num];        int low = 0;        int high = num - 1;        int mid;        while(low <= high){//查找出需要插入的位置(即low位置)            mid = (low + high) / 2;            if(x < array[mid]){                high = mid - 1;            }else{                low = mid + 1;            }        }        //将插入点(含插入点)之后的数据全部后移一位        for(int i = num - 1; i >= low; --i){            array[i+1] = array[i];        }        //将数据插入        array[low] = x;    }

2.3 希尔排序:

① 思想:又称缩小增量排序法。把待排序序列分成若干较小的子序列,然后逐个使用直接插入排序法排序,最后再对一个较为有序的序列进行一次排序,主要是为了减少移动的次数,提高效率。原理应该就是从无序到渐渐有序,要比直接从无序到有序移动的次数会少一些。

② 时间复杂度:O(n的1.5次方)

③ 空间复杂度:O(1)

④ 稳定性:不稳定排序。{2,4,1,2},2和1一组4和2一组,进行希尔排序,第一个2和最后一个2会发生位置上的变化。

⑤ 程序:

    public static void  ShellSort(int[] array, int length){        int temp;   //保存临时数据(数据交换时使用)        int step;   //设置增量        for(step = length/2; step > 0; step /= 2){  //增量循环递减直至到1            for(int i = 0; i < step; i++){          //遍历全部子序列                for(int j = i + step; j < length; j += step){   //遍历每个子序列中的元素                    if(array[j] < array[j-step]){//直接插入排序                        temp = array[j];                        int n = j - step;                        while(n >= 0 && array[n] > temp){                            array[n + step] = array[n];                            n -= step;                        }                        array[n + step] = temp;                    }                }            }        }    }

3.交换类排序

3.1 冒泡排序:

① 思想:反复扫描待排序序列,在扫描的过程中顺次比较相邻的两个元素的大小,若逆序就交换位置。第一趟,从第一个数据开始,比较相邻的两个数据,(以升序为例)如果大就交换,得到一个最大数据在末尾;然后进行第二趟,只扫描前n-1个元素,得到次大的放在倒数第二位。以此类推,最后得到升序序列。如果在扫描过程中,发现没有交换,说明已经排好序列,直接终止扫描。所以最多进行n-1趟扫描。

② 时间复杂度:T(n) = O(n²)。

③ 空间复杂度:S(n) = O(1)。

④ 稳定性:稳定排序。

⑤ 程序:

    public static void  BubbleSort(int[] array, int length){        int temp;       //保存临时数据(数据交换时使用)        Boolean flag;       //设置标记,记录此趟排序是否发生交换        for(int i = 0; i < length-1; i++){  //设置需要扫描的趟数            flag = false;            for(int j = 0; j < length - i - 1; j++){    //遍历,比相邻两个数据的大小                if(array[j] < array[j+1]){                    temp = array[j+1];                    array[j+1] = array[j];                    array[j] = temp;                    flag = true;                }            }        }    }

3.2 快速排序:

① 思想:冒泡排序一次只能消除一个逆序,为了能一次消除多个逆序,采用快速排序。以一个关键字为轴,从左从右依次与其进行对比,然后交换,第一趟结束后,可以把序列分为两个子序列,轴的一边全是比它小的(或者小于等于),轴的另一边全是比它大的(或者大于等于),然后再分段进行快速排序,达到高效。

② 时间复杂度:平均T(n) = O(n㏒n),最坏O(n²)。

③ 空间复杂度:S(n) = O(㏒n)。

④ 稳定性:不稳定排序。{3, 2, 2}

⑤ 程序:

    public static void  QuickSort(int[] array, int low, int high){//对从array[low]到array[high]之间的元素进行排序        int temp = array[low]; //保存进行比较的关键值        int i = low,j = high;  //i指向最开始索引,j指向最末尾索引        if(i<j){            //下面的这个循环完成一趟排序,即将数组中小于temp的元素放置在左边,大于temp的元素放置在右边            while(i != j){                while(i < j && array[j] > temp) --j;    //从最后索引递减,直到遇到小于temp的元素                if(i < j){//将小于temp的元素,放置到数组最前面                    array[i] = array[j];                    ++i;                }                while(i<j && array[i] < temp) ++i;  //从前面的索引递增,直到遇到大于temp的元素                if(i < j){//将大于temp的元素,放置在数组后面                    array[j] = array[i];                    --j;                }            }            //数组中相比较于temp已经划分分到2边,将temp放置到最终中间位置            array[i] = temp;            QuickSort(array, low, i-1);//递归对temp左边的元素进行排序            QuickSort(array, i+1, high);//递归对temp右边的元素进行排序        }    }

4.选择类排序

4.1 简单选择排序:

① 思想:第一趟时,从第一个记录开始,通过n – 1次关键字的比较,从n个记录中选出关键字最小的记录,并和第一个记录进行交换。第二趟从第二个记录开始,选择最小的和第二个记录交换。以此类推,直至全部排序完毕。

② 时间复杂度:T(n) = O(n²)。

③ 空间复杂度:S(n) = O(1)。

④ 稳定性:不稳定排序,{3, 3, 2}。

⑤ 程序:

    public static void  SelectSort(int[] array, int length){        int k;  //保存目标元素的索引        int temp; //临时变量(数据交换时候使用)        for(int i = 0; i < length; i++){    //对数组中的每一个数进行遍历            k = i;            for(int j = i+1; j < length; j++){                if(array[k] > array[j]){//选择出目前最小元素的索引                    k = j;                }            }            if(k != i){                temp = array[i];                array[i] = array[k];                array[k] = temp;            }        }    }

4.2 堆排序

① 思想:把待排序记录的关键字存放在数组r[1…n]中,将r看成是一刻完全二叉树的顺序表示,每个节点表示一个记录,第一个记录r[1]作为二叉树的根,一下个记录r[2…n]依次逐层从左到右顺序排列,任意节点r[i]的左孩子是r[2i],右孩子是r[2i+1],双亲是r[i/2向下取整]。然后对这棵完全二叉树进行调整建堆。

② 时间复杂度:T(n) = O(n㏒n)。

③ 空间复杂度:S(n) = O(1)。

④ 稳定性:不稳定排序。{5, 5, 3}

⑤ 程序:

/** * 堆排序 *//** * 1.完成数组在array[low]到array[high]范围内对位置在low上的结点进行调整 * @param array * @param low * @param high */public static void  Sift(int[] array, int low, int high){    int i = low,j = low*2;  //array[j]为array[i]的左子节点    int temp = array[i];    //获取low上的值    while(j <= high){        if(j < high && array[j] < array[j+1]){//如果右子节点比左子节点大,那么j指向右子节点            ++j;        }        if(array[j] > temp){            array[i] = array[j];    //将array[j]移动到其双亲结点上            i = j;                  //修改i与j的值,以便继续向下调整            j = i * 2;        }else            break;    }    array[i] = temp;                //被调整结点放入最终位置}   /** * 2.堆排序函数 * @param array * @param length */public static void  HeapSort(int[] array, int length){    int temp;    for(int i = length / 2; i >=0 ;--i)     //从length/2先上遍历,建立初始堆。(根据完全二叉树原理,length/2均为叶子结点)        Sift(array, i, length-1);    for(int i = length-1; i>=1;--i){        //进行遍历,每一次将根节点中的元素取出来,然后放入最终位置。        temp = array[i];        array[i] = array[0];        array[0] = temp;        Sift(array,0,i-1);                  //在减少1个元素的序列中进行调整    }}

5. 归并类排序

5.1 二路归并排序

① 思想:假设初始序列右n个记录,首先将这n个记录看成n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2向上取整 个长度为2(n为奇数时,最后一个序列的长度为1)的有序子序列。在此基础上,在对长度为2的有序子序列进行两两归并,得到若干个长度为4的有序子序列。如此重复,直至得到一个长度为n的有序序列为止。

② 时间复杂度:T(n) = O(n㏒n)。

③ 空间复杂度:S(n) = O(n)。

④ 稳定性:稳定排序。

⑤ 程序:

/* * 二路归并排序 *//** * 1.归并数组操作 * 已知array1[low...mid]和array1[mid + 1...high]分别按关键字有序排列, * 将它们合并成一个有序序列,存放在array2[low...high] * @param array1 * @param low * @param mid * @param high * @param array2 */public static void  Merge(int[] array1, int low, int mid, int high , int[] array2){    /**/      int i = low;      int j = mid + 1;      int k = low;    while((i <= mid) && (j <= high)){ //2个数组进行合并操作         if(array1[i] <= array1[j])          {              array2[k] = array1[i];              ++i;          }          else          {              array2[k] = array1[j];              ++j;          }          ++k;      }      while(i <= mid){    //如果还有其中一个数组剩余元素,直接加在末尾        array2[k] = array1[i];          k++;          i++;      }      while(j <= high){  //如果还有其中一个数组剩余元素,直接加在末尾        array2[k] = array1[j];          k++;          j++;      }  }/** * 2. r1[low...high]经过排序后放在r3[low...high]中,r2[low...high]为辅助空间 * @param r1 * @param low * @param high * @param r3 */public static void MSort(int r1[], int low, int high, int r3[]){      int[] r2 = new int[high+1];    int mid;    if(low == high)          r3[low] = r1[low];      else{          mid = (low + high) / 2;          MSort(r1, low, mid, r2);          MSort(r1, mid + 1, high, r2);          Merge(r2, low, mid, high, r3);      }  }  /** * 3.合并排序 * 对记录数组r[0...n]做归并排序 * @param array * @param length */public static void MergeSort(int array[], int length){      MSort(array, 0, length-1, array);  }

6. 基数类排序

6.1 链式基数排序

① 思想:先分配,再收集,就是先按照一个次关键字收集一下,然后进行收集(第一个排序),然后再换一个关键字把新序列分配一下,然后再收集起来,又完成一次排序,这样所有关键字分配收集完后,就完成了排序。

② 时间复杂度:T(n) = O( d ( n + rd ) )。

③ 空间复杂度:S(n) = O(rd)。

④ 稳定性:稳定排序。

7. 总结

(1)简单排序法一般只用于n较小的情况(例如n<30)。当序列的记录“基本有序”时,直接插入排序是最佳的排序方法。如果记录中的数据较多,则应采用移动次数较少的简单选择排序法。

(2)快速排序、堆排序和归并排序的平均时间复杂度均为O(n㏒n),但实验结果表明,就平均时间性能而言,快速排序是所有排序方法中最好的。遗憾的是,快速排序在最坏情况下的时间性能为O(n²)。堆排序和归并排序的最坏时间复杂度仍为O(n㏒n),当n较大时,归并排序的时间性能优于堆排序,但它所需的辅助空间最多。

(3)可以将简单排序法与性能较好的排序方法结合使用。例如,在快速排序中,当划分子区间的长度小于某值时,可以转而调用直接插入排序法;或者先将待排序序列划分成若干子序列,分别进行直接插入排序,然后再利用归并排序法,将有序子序列合并成一个完整的有序序列。

(4)基数排序的时间复杂度可以写成O(d·n)。因此,它最适合于n值很大而关键字的位数d较小的序列。当d远小于n时,其时间复杂度接近O(n)。

(5)从排序的稳定性上来看,在所有简单排序法中,简单选择排序是不稳定的,其他各种简单排序法都是稳定的。然而,在那些时间性能较好的排序方法中,希尔排序、快速排序、堆排序都是不稳定的,只有归并排序、基数排序是稳定的。

参考:

书籍:《2017版数据结构高分笔记》 率辉 机械工业出版社
博客:http://blog.csdn.net/wzyhb123456789/article/details/5974790#

0 0