排序算法

排序概论
假设含有n个记录的序列为{R1,R2,...Rn},其相应的关键字序列为{K1,K2,...Kn}。将这样的记录重新排序为{Ri1,Ri2,...Rin},使得相应的关键字值满足条件Ki1<=Ki2<=...<=Kin,这样一种操作称为排序。

通常来说,排序的目的是快速查找

对于一个排序算法来说,一般从一下3方面衡量算法的优势。
时间复杂度:主要分析关键字的比较次数和记录的移动次数
空间复杂度:分析排序算法中需要多少辅助内存
稳定性:如果两个记录A和B关键字值相等,但排序后A、B的先后次数保持不变,则称这种算法是稳定的。

外部排序:
如果参与排序的数据元素非常多,数据量非常大,计算无法把整个排序过程放在内存中完成,必须借助外部存储器(如磁盘),这种排序就被称为外部排序。
外部排序最常用的是多路归并算法,将源文件分解分别排序,然后对有序的多个子文件进行归并并排序。
实际上也可以认为外部排序是由多次内部排序组成的。

内部排序:
分类:
选择排序(直接选择排序、堆排序)
交换排序(冒泡排序、快速排序)
插入排序(直接插入排序、折半插入排序、Shell排序)
归并排序
桶式排序
基数排序

 

-----------------------------------------------------------------------------

直接选择排序:
思路:
第1次,用第1个数据依次和后面的每个数据进行比较,如果第一个数据大于后面的某个数据,交换它们...依次类推(就是交换后的元素替代第一个数据和后面的进行比较,交换。。),第一轮得到最小的首位。
第2次比较,从第2个数据开始得到第二小的数据放在第二位。
...
n01次比较,选出数据第n-1小(第二大)的数据,排在n-1位(倒数第一位),剩下的就是最大的数据,它排在最后。

优点:
算法简单,容易实现
缺点:
一次只能确定一个元素,n个元素需要n-1次比较

 

代码:

class DataWrap implements Comparable<DataWrap>{
    int data;
    String flag;
    public DataWrap(int data , String flag){
        this.data = data;
        this.flag = flag;
    }
    public String toString(){
        return data + flag;
    }
    public int compareTo(DataWrap dw) {
        return this.data >dw.data ? 1 : (this.data ==dw.data ? 0 : -1);
    }
}
public class SelectSort {
    public static void selectSort(DataWrap[] data){
        System.out.println("开始排序");
        int arrayLength = data.length;
        for(int i = 0; i <arrayLength -1; i++){

            for(int j = i + 1 ;j < arrayLength; j++){
                if(data[i].compareTo(data[j]) > 0){
                    DataWrap temp = data[i];
                    data[i] = data[j];
                    data[j] = temp;
                }
            }
            System.out.println(java.util.Arrays.toString(data));
        }
    }

    public static void selectSort1(DataWrap[] data){ //优化后排序代码,减少交换次数
        System.out.println("开始排序");
        int arrayLength = data.length;
        for(int i = 0; i <arrayLength -1; i++){
            int minIndex = i;
            for(int j = i + 1 ;j < arrayLength; j++){
                if(data[i].compareTo(data[j]) > 0){
                    minIndex = j;
                }
            }
            if(minIndex != i){
                DataWrap temp = data[i];
                data[i] = data[minIndex];
                data[minIndex] = temp;
            }
            System.out.println(java.util.Arrays.toString(data));
        }
    }

    public static void main(String[] args){
        DataWrap[] data = {
            new DataWrap(21 , ""),
            new DataWrap(30 , ""),
            new DataWrap(49 , ""),
            new DataWrap(30 , "*"),
            new DataWrap(16 , ""),
            new DataWrap(9 , ""),
        };
        System.out.println("排序前:\n" + java.util.Arrays.toString(data));
        selectSort(data);
        System.out.println("排序后:\n" + java.util.Arrays.toString(data));
    }
}

通过结果可以知道,两个data为30的位置改变了,该排序算法是不稳定的。

直接选择排序如果n个元素,交换次数n-1次,比较次数多,总体来说,时间效率为O(2^n)

直接选择排序的空间效率很高,只需要一个附加程序单元用于交换,其空间效率为O(1)

 

------------------------------------------------------------------------------------

 

堆排序
堆有关的概念:
假设有n个数据元素的序列k0,k1,...,k(n-1),当且仅当满足如下关系时,可以将这组数据成为小堆顶:
ki <= k(2i+1)且ki<=k(2i+2)
或者,满足如下关系时,可以将这组数据称为大顶堆:
k1>=k(2i+1)且ki>=k(2i+2)
对于满足小顶堆的数据列顺序排成一棵完全二叉树,则此树的特点是:树中所有节点的值都小于其左右节点的值,此树的根节点的值必然最小。反之,满足大顶堆的数据排成的完全二叉树根节点的值必然最大,小顶堆的任何子树也是小顶堆,大顶堆的任何子树也是大顶堆

堆排序的步骤就是重复以下2步:
1、建堆
2、拿堆的根节点和最后一个节点交换

package com.test_two;

import java.util.Arrays;

/**
 * Created by Administrator on 2017/3/13.
 */
public class HeapSort {
    public static void heapSort(DataWrap[] data){
        System.out.println("开始排序");
        int arrayLength = data.length;
        for(int i = 0; i < arrayLength - 1; i++){
            buildMaxHeap(data , arrayLength - 1 - i);
            swap(data , 0 , arrayLength - 1 - i);
            System.out.println(Arrays.toString(data));
        }

    }
    /**
     * 建大顶堆
     */
    private static void buildMaxHeap(DataWrap[] data , int lastIndex){
        for(int i = (lastIndex - 1) / 2; i >= 0; i-- ){
            /**
             * 从最后一个父节点开始一直到最后
             */
            int k = i;
            while(k*2 + 1 <= lastIndex){
                int biggerIndex = 2 * k + 1;
                if(biggerIndex < lastIndex){
                    /**
                     * 左子节点和右子节点比较,找出最大的子节点
                     */
                    if(data[biggerIndex].compareTo(data[biggerIndex + 1]) < 0){
                        biggerIndex++;
                    }
                }
                if(data[k].compareTo(data[biggerIndex]) < 0){
                    /**
                     * 最大子节点和父节点比较,如果父节点小于的话交换位置
                     */
                    swap(data , k , biggerIndex);
                    /**
                     * 将父节点赋值为最大的节点,这个不知道什么意思,为了跳出循环吗?
                     */
                    k = biggerIndex;
                }else{
                    break;
                }
            }
        }
    }
    private static void swap(DataWrap[] data , int i , int j){
        DataWrap tmp = data[i];
        data[i] = data[j];
        data[j] = tmp;
    }
public static void main(String[] args){
DataWrap[] data = new DataWrap[]{
new DataWrap(21 , ""),
new DataWrap(30 , ""),
new DataWrap(49 , ""),
new DataWrap(30 , "*"),
new DataWrap(21 , "*"),
new DataWrap(16 , ""),
new DataWrap(9 , ""),
};
System.out.println("排序之前:\n" + Arrays.toString(data));
heapSort(data);
System.out.println("排序之后:\n" + Arrays.toString(data));
}
}

得到结果:

堆排序算法而言,假设有n项数据,需要进行n-1次建堆,每次建堆耗时为log2^n,则其时间效率为O(nlog2^n)

堆排序算法空间效率很高,它只需要一个附加程序单元用于交换,其空间效率为O(1)

 

--------------------------------------------------------
交换排序
冒泡排序:
思路:水泡,越来越大,第一轮过后最大值到末尾。先第一个和第二个比较,大的放后位,不满足交换,之后第二个和第三个比较...
最坏的情况n-1轮过去就ok了,如果有一轮没有交换,则不需要比较了。

package com.test_two;

import java.util.Arrays;

/**
 * Created by Administrator on 2017/3/14.
 */
public class BubbleSort {
    public static void bubbleSort(DataWrap[] data){
        System.out.println("开始排序");
        int arrLength = data.length;
        for(int i = 0; i < arrLength - 1; i++){
            boolean flag = false;
            for(int j = 0; j <arrLength - 1 - i ; j++){
                if(data[j].compareTo(data[j + 1]) > 0){
                    DataWrap tmp = data[j + 1];
                    data[j + 1] = data[j];
                    data[j] = tmp;
                    flag = true;
                }
            }
            System.out.println(Arrays.toString(data));
            //如果某趟没有发生交换,则表示已经处于有序状态
            if(!flag){
                break;
            }
        }
    }

    public static void main(String[] args){
        DataWrap[] data = new DataWrap[]{
                new DataWrap(9 , ""),
                new DataWrap(16 , ""),
                new DataWrap(21 , "*"),
                new DataWrap(23 , ""),
                new DataWrap(30 , ""),
                new DataWrap(49 , ""),
                new DataWrap(21 , ""),
                new DataWrap(30 , "*")
        };
        System.out.println("排序前:\n" + Arrays.toString(data));
        bubbleSort(data);
        System.out.println("排序后:\n" +Arrays.toString(data));
    }
}

 

 

冒泡排序而言时间效率是不确定的:最好的情况下,执行一趟冒泡即可,无需做任何交换。最坏的情况下,需要执行n-1趟。
冒泡排序的空间效率很高,只需要一个附加程序单元用于交换,其空间效率为O(1).
冒泡排序是稳定的。

 ------------------------------------------------------------------------------------------------------------------------------

快速排序:
快速排序是一个速度非常块的交换排序方法,它的基本思路很简单:从待排的数据序列中任取一个数据(如第一个数据)作为分界线,所有比他小的数据一律放在左边,所有比他大的数据一律放在右边。经过这样一趟下来,该序列形成左右两个子序列,左序列中元素的值都比分界值小,右序列中元素值都比分界值大。
接下来对左右两个子序列进行递归,重新选择中心排序并依次规律调整,直到每一个子表元素只剩一个,排序完成。
快速排序的关键在于:
1、找出指定的分界值
2、将所有比分界值小的数据元素放在左边(不要被影响,左边代表的不是开始的分界值的位置左边,而是最后才会知道分界点的文职)
3、将所有比分解值大的数据元素放在右边

  两种方式现在:

    1、方式如所http://baidu.ku6.com/watch/8495694393207415318.html展示的

    2、如图:

      

代码:

package com.test_two;

import java.util.Arrays;

/**
 * Created by Administrator on 2017/3/14.
 */
public class QuickSort {
    private static void swap(DataWrap[] data , int i , int j){
        DataWrap tmp;
        tmp = data[i];
        data[i] = data[j];
        data[j] = tmp;
    }

  //方式2
private static void subSort(DataWrap[] data , int start , int end){ if(start < end){ DataWrap base = data[start]; int i = start; int j = end + 1; while(true){ while(i < end && data[++i].compareTo(base) <= 0); while(j > start && data[--j].compareTo(base) >= 0); if(i < j){ swap(data , i , j); }else{ break; } } swap(data , start , j); subSort(data , start , j - 1); subSort(data , j + 1 , end); } } public static void quickSort(DataWrap[] data){ subSort(data , 0 , data.length - 1); }
  //方式1
public static void testQuickSort(DataWrap[] datas , int start , int end){ int base = start , i = start; int j = end; while( true){ if(i >= j){ break; } if(base <= i){ if(datas[base].compareTo(datas[j]) > 0){ swap(datas , base , j); i++; base = j; }else{ j--; } }else{ if(datas[i].compareTo(datas[base]) > 0){ swap(datas , i , base); j--; base = i; }else{ i++; } } } System.out.println(Arrays.toString(datas) + " " + datas[base]); testQuickSort(datas , start , base - 1); testQuickSort(datas , base + 1 , end); } public static void main(String[] args){ DataWrap[] data = new DataWrap[]{ new DataWrap(9 , ""), new DataWrap(-16 , ""), new DataWrap(21 , "*"), new DataWrap(23 , ""), new DataWrap(-30 , ""), new DataWrap(-49 , ""), new DataWrap(21 , ""), new DataWrap(30 , "*"), new DataWrap(13 , "*"), }; // System.out.println("排序之前:\n" + Arrays.toString(data)); subSort(data , 0 , data.length - 1); // System.out.println("排序之后:\n" + Arrays.toString(data)); } }

----------------------------------------------------------------------------------------------------

插入排序
插入排序也是一种常见的排序方法,它主要包括直接插入、Shell排序和折半插入等几种常见的排序方法。

直接插入排序:
思路:一次将待排序的数据元素按其关键字值的大小插入前面的有序序列。
1、从第2个元素开始拿起,一个元素一定有序,和第一个元素比较,然后插入
2、第3个和前面的有序进行比较插入到合适的位置
3、依次执行到最后一个元素。

我脑子不好使,妈蛋。

package com.test_two;

import java.util.Arrays;

/**
 * Created by Administrator on 2017/3/14.
 */
public class InsertSort {

  //这是标准做法
public static void insertSort(DataWrap[] data){ System.out.println("开始排序:\n"); int arrLength = data.length; for(int i = 1; i < arrLength - 1; i++){ DataWrap tmp = data[i]; //如果当前元素<前一格元素,说明在前面的有序序列中有一个位置应该属于data[i],且序列要在该位置之后全部后移一段距离 if(data[i].compareTo(data[i - 1]) < 0){ //倒序,这样的话方便推移 int j = i - 1; //如果当前位置比指定元素大,则向后走一位 for( ; j >= 0 && data[j].compareTo(tmp) > 0 ;j--){ data[j + 1] = data[j]; } //这个时候j已经不再是i - 1 了,而是比data[i]小的那个数的下表,所以后一位是i对应元素的存放地址 data[j + 1] = tmp; } } } /** * 我写的插入用了三个循环,应该很容易看出思路 * @param data */ public static void myInsert1(DataWrap[] data){ for(int j = 0; j < data.length; j++){ for(int i = 0; i < j; i++){ if(data[j].compareTo(data[i]) < 0){ DataWrap tmp = data[j]; int z = j -i; while(z > 0){ data[i+z] = data[i+z-1]; z--; } data[i] = tmp; } } } } public static void main(String[] args){ DataWrap[] data = new DataWrap[]{ new DataWrap(21 , ""), new DataWrap(30 , ""), new DataWrap(49 , ""), new DataWrap(30 , "*"), new DataWrap(21 , "*"), new DataWrap(16 , ""), new DataWrap(9 , ""), }; System.out.println("排序之前:\n" + Arrays.toString(data)); myInsert1(data); System.out.println("排序之后:\n" + Arrays.toString(data)); } }

 

-----------------------------------------------------------------------------------------------------------------------

折半插入排序:
折半插入排序是对直接插入排序的改进。对于简单插入排序而言,当第i-1趟需要将第i个元素插入到前面的0~i-1个元素序列时,它总是从i-1个元素开始,逐步比较每个元素,直到找到它的位置。这个没有利用前面0~i-1元素已经有序的特点,折半插入排序则改进了这一点。
做法:
1、计算0~i-1索引的中间点,也就是用i索引处的元素和(0+i-1)/2索引处的元素进行比较,如果i索引处的元素大,就直接在(0+i-1)/2半个范围内搜索,繁殖,就在0~(0+i-1)/2半个范围内搜索,这就是所谓的折半。
2、在半个范围内搜索时,再按(1)的方法进行折半搜索。总是不断地折半,这样就可以将范围缩小到1/2,1/4,1/8,从而快速确定第i个元素的插入位置
3、确定了第i个元素的插入位置,剩下的事情就简单了。程序将该位置以后的元素整体后移一位,然后将第i个元素放入该位置。

代码:

package com.test_two;

import java.util.Arrays;

/**
 * Created by Administrator on 2017/3/14.
 */
public class BinaryInsertSort {
    public static void binaryInsertSort(DataWrap[] data){
        System.out.println("开始排序:\n");
        int arrLength = data.length;
        for(int i = 1; i < arrLength; i++){
            DataWrap tmp = data[i];
                int low = 0;
                int high = i - 1;
        //不停折半,找到i应该插入的位置,这里的前提是前面是一个有序序列。所以i从1开始
while(low <= high){ int mid = (low + high)/2; if(tmp.compareTo(data[mid]) > 0){ low = mid + 1; }else{ high = mid - 1; } } for(int j = i; j > low; j--){ data[j] = data[j - 1]; } data[low] = tmp; } } public static void main(String[] args){ DataWrap[] data = new DataWrap[]{ new DataWrap(21 , ""), new DataWrap(30 , ""), new DataWrap(49 , ""), new DataWrap(30 , "*"), new DataWrap(21 , "*"), new DataWrap(16 , ""), new DataWrap(9 , ""), }; System.out.println("排序之前:\n" + Arrays.toString(data)); binaryInsertSort(data); System.out.println("排序之后:\n" + Arrays.toString(data)); } }

------------------------------------------------------------
Shell排序
于直接插入排序而言,当插入排序执行到一半时,待插入左边的所有数据都是已经处于有序状态,直接插入排序将待插值存储在一个临时变量里。然后,从待插值左边第一个数据单元开始,只要该数据单元的值大于待插值,该单元格就右移一格,知道找到第一个小于待插值的数据单元。接下来,将临时变量里的值放入小于待查值的数据单元之后。
上面算法可以发现一个问题:如果一个很小的数据单元唯一很靠右的位置上,为了把这个数据单元移动到左边正确的位置上,中间所有的数据单元都需要向右移动一格。这个步骤对每个数据项打偶执行了近N次复制。插入排序的执行效率是O(N^2)。
Shell排序对直接插入排序进行了简单改进:它通过加大插入排序中元素之间的间隔,并且在这些有间隔的元素中进行插入排序,从而使数据项大跨度地移动。当这些数据项一次排序后,Shell算法减小数据项的间隔再排序,依次进行下去。

一句话描述:

希尔排序是直接插入排序的变体,由多个直接插入排序组成。增量变化。

可以说直接插入排序就是增量为1的希尔排序。

希尔排序比直接插入排序快得多,h比较大的时候,移动得少,移动距离长,效率高,h减小,每一次移动的项增多,但是此时数据项已经接近于他们排序后的最终位置,这对于插入排序更有效率。两种情况的结合使得Shell排序效率这么高

常用的h序列由kunth提出,该序列从1开始,通过以下公式产生:

  h = 3 * h+1;

有些地方h取值数列的半,然后一直取半,直到1

代码:

package com.test_two;

import java.util.Arrays;

/**
 * Created by Administrator on 2017/3/15.
 */
public class ShellSort {
    public static void shellSort(DataWrap[] data){
        System.out.println("开始排序");
        int arrayLength = data.length;
        int h = 1;
        //这里h的值是最后为1值为h * 3 + 1.
        while(h <= arrayLength/3){
            h = h * 3 + 1;
        }
        while(h > 0){
            System.out.println("====h的值:" + h + "====");
            for(int  i = h; i < arrayLength; i++){
                DataWrap tmp = data[i];
                if(data[i].compareTo(data[i - h]) < 0){
                    int j = i - h;
                    for(; j >= 0 && data[j].compareTo(tmp) > 0; j-=h ){
                        data[j + h] = data[j];
                    }
                    data[j + h] = tmp;
                }
            }
            h = (h - 1) / 3;
        }
    }

    public static void main(String[] args){
        DataWrap[] data = new DataWrap[]{
                new DataWrap(9 , ""),
                new DataWrap(-16 , ""),
                new DataWrap(21 , "*"),
                new DataWrap(23 , ""),
                new DataWrap(-30 , ""),
                new DataWrap(-49 , ""),
                new DataWrap(21 , ""),
                new DataWrap(30 , "*"),
                new DataWrap(30 , "")
        };
        System.out.println("排序之前:\n" + Arrays.toString(data));
        shellSort(data);
        System.out.println("排序之后:\n" + Arrays.toString(data));
    }
}

----------------------------------------------
归并排序
归并排序的基本思想是将两个(或以上)的有序序列合并为一个新的有序序列。
细化来说:归并排序先将长度为n的无序序列看成是n个长度为1的有序子序列,首先做两两合并,得到n/2个长度为2的有序子序列,再做两两合并。。。,不断重复这个过程,最终可以得到一个长度为n的有序序列。

难点在于合并:
1、定义变量i,i从0开始,一次等于A序列中每个元素的索引
2、定义变量j,j从0开始,一次等于B序列中每个元素的索引
3、拿A序列中i处元素和b序列中j索引处的元素进行比较,将比较小的复制到另一个临时数组中
4、如果i索引处的元素小,i++;如果j索引处的元素小j++

我想说一句:其实我并不觉得合并难,思路我也想到了,就是看插入的跳舞视频的那种比较,但是尼玛,书上的代码忒专业了点,都优化过,以至于我看不出写的是什么,擦擦。

代码:

package com.test_two;

import java.util.Arrays;

/**
 * Created by Administrator on 2017/3/15.
 */
public class MergeSort {
    public static void mergeSort(DataWrap[] data){
        sort(data , 0 , data.length - 1);
    }
    private static void sort(DataWrap[] data , int left , int right){
        if(left < right){
            int center = (left + right)/2;
            sort(data , center + 1 , right);
            sort(data , left , center);
            merge(data , left , center , right);
        }
    }
    private static void merge(DataWrap[] data , int left , int center , int right){
        DataWrap[] tmpArr = new DataWrap[data.length];
        int mid = center + 1;
        int third = left;
        int tmp = left;
        //将两个数组中ij重合处的代码从小到大放入临时数组中,留下来的数据都比临时数据中大,可能留下的是i的数据也可能是j的数据,因为不一定有顺序地一一排序过去
        while(left <= center && mid <= right){
            if(data[left].compareTo(data[mid]) < 0){
                tmpArr[third++] = data[left++];
            }else{
                tmpArr[third++] = data[mid++];
            }
        }
        //如果是右边的数据有剩余的话,将剩下的右边数据放入临时数组的对应位置
        while(mid <= right){
            tmpArr[third++] = data[mid++];
        }
        //反之,如果左边的数据有剩余的话,将左边的数据放入临时数组的对应位置
        while(left <= center){
            tmpArr[third++] = data[left++];
        }

        //将临时数组的left~right的内容复制到初始数组
        while(tmp <= right){
            data[tmp] = tmpArr[tmp++];
        }
        
        tmpArr = null;
    }

    public static void main(String[] args){
        DataWrap[] data = new DataWrap[]{
                new DataWrap(9 , ""),
                new DataWrap(-16 , ""),
                new DataWrap(21 , "*"),
                new DataWrap(23 , ""),
                new DataWrap(-30 , ""),
                new DataWrap(-49 , ""),
                new DataWrap(21 , ""),
                new DataWrap(30 , "*"),
                new DataWrap(13 , "*"),
        };
        System.out.println("排序之前:\n" + Arrays.toString(data));
        mergeSort(data);
        System.out.println("排序之后:\n" + Arrays.toString(data));
    }
}

--------------

桶式排序和基数排序有点懵,不想看了。

posted @ 2017-03-06 17:52  guodaxia  阅读(154)  评论(0)    收藏  举报