1. 程式人生 > >資料結構和演算法解析:排序問題簡易總結

資料結構和演算法解析:排序問題簡易總結

直接插入排序

直接插入排序(Straight Insertion Sorting)的基本思想:在要排序的一組數中,假設前面(n-1) [n>=2] 個數已經是排好順序的,現在要把第n個數插到前面的有序數中,使得這n個數也是排好順序的。如此反覆迴圈,直到全部排好順序。 直接插入排序是一種穩定的排序方法,最好的時間複雜是O(n)也就是已經排好的序列,最差的時間複雜度是O(n^2)逆序

    //插入排序是一種穩定的排序方法,最好的時間複雜是O(n)也就是已經排好的序列,最差的時間複雜度是O(n^2)逆序
    public void insertSort(int nums[]){
        int
tmp; for(int i=1;i<nums.length;i++){ int j=i; while (j>0 && nums[j]<nums[j-1]){ tmp=nums[j]; nums[j]=nums[j-1]; nums[j-1]=tmp; j--; } } }

希爾排序

  • 希爾排序(Shell’s Sort)是插入排序的一種又稱“縮小增量排序”(Diminishing Increment Sort),是直接插入排序演算法的一種更高效的改進版本。
  • 希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序演算法排序;隨著增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個檔案恰被分成一組,演算法便終止。

演算法思想

對於直接插入排序問題,資料量巨大時。將數的個數設為n,取奇數k=n/2,將下標差值為k的數分為一組,構成有序序列。再取k=k/2 ,將下標差值為k的書分為一組,構成有序序列。重複第二步,直到k=1執行簡單插入排序。

希爾排序是一種非常不穩定的排序方法,它的時間複雜度為O(n^(1.3—2))

//希爾排序是一個不穩定的排序演算法,它的時間複雜度為O(n^(1.3—2))
    public void shellSortHelper
(int nums[],int incr){ int tmp; for(int i=incr;i<nums.length;i+=incr){ int j=i; while (j>0 && nums[j]<nums[j-incr]){ tmp=nums[j]; nums[j]=nums[j-incr]; nums[j-incr]=tmp; j-=incr; } } } //希爾排序 public void shellSort(int nums[]){ for(int i=nums.length/2;i > 0;i/=2){ shellSortHelper(nums,i); } }

選擇排序

  1. 常用於取序列中最大最小的幾個數時。 (如果每次比較都交換,那麼就是交換排序;如果每次比較完一個迴圈再交換,就是簡單選擇排序。)

  2. 遍歷整個序列,將最小的數放在最前面。

  3. 遍歷剩下的序列,將最小的數放在最前面。

  4. 重複第二步,直到只剩下一個數。

選擇排序的時間複雜度為O(n^2) 選擇排序不是一個穩定的排序

//交換排序是一種穩定的排序方法,最好最壞的時間複雜度都是O(n^2)
    public void swapSort(int nums[])
    {
        int tmp;
        for(int i=0;i<nums.length;i++){
            for(int j=i+1;j<nums.length;j++){
                if(nums[i]>nums[j]){
                    tmp=nums[j];
                    nums[j]=nums[i];
                    nums[i]=tmp;
                }
            }
        }
    }

    //選擇排序是一種不穩定的排序方法,最好最壞的時間複雜度都是O(n^2)
    public void selectSort(int nums[]){
        for(int i=0;i<nums.length;i++){
            int values=nums[i];
            int position=i;
            for(int j=i+1;j<nums.length;j++){
                if(nums[j]<values){
                    values=nums[j];
                    position=j;
                }
            }
            //swap
            nums[position]=nums[i];
            nums[i]=values;
        }
    }

氣泡排序

很簡單,用到的很少,據瞭解,面試的時候問的比較多!將序列中所有元素兩兩比較,將最大的放在最後面。將剩餘序列中所有元素兩兩比較,將最大的放在最後面。重複第二步,直到只剩下一個數。

氣泡排序的時間複雜度為O(n^2) 氣泡排序是一個穩定的排序

    //氣泡排序 一種穩定的排序演算法,最好最壞的時間複雜度都是O(n^2)
  public void bubbleSort(int nums[]){
        int tmp;
        for(int i=0;i<nums.length;i++){
            for (int j=0;j<nums.length-i-1;j++){
                if(nums[j]>nums[j+1]){
                    tmp=nums[j+1];
                    nums[j+1]=nums[j];
                    nums[j]=tmp;
                }
            }
        }
  }

快速排序

要求時間最快時。

  1. 選擇第一個數為p,小於p的數放在左邊,大於p的數放在右邊。
  2. 遞迴的將p左邊和右邊的數都按照第一步進行,直到不能遞迴。

快速排序是由一種不穩定的排序演算法,最好的時間複雜度是O(nLogN),最差時間複雜度是O(n^2)

 //快速排序是由一種不穩定的排序演算法,最好的時間複雜度是O(nLogN),最差時間複雜度是O(n^2)
    private void quickSortHelper(int nums[],int start,int end){
      if(start>end) return;
      int high=end;
      int low=start;
      int target=nums[start];
      while (low<high){
          while (low<high && nums[high]>=target){
              high--;
          }
          nums[low]=nums[high];
          while (low<high && nums[low]<=target){
              low++;
          }
          nums[high]=nums[low];
      }
      nums[low]=target;
      quickSortHelper(nums,start,low-1);
      quickSortHelper(nums,low+1,end);
    }

    public void quickSort(int nums[])
    {
        quickSortHelper(nums,0,nums.length-1);
    }

歸併排序

速度僅次於快速排序,記憶體少的時候使用,可以進行平行計算的時候使用。

  1. 選擇相鄰兩個陣列成一個有序序列。
  2. 選擇相鄰的兩個有序序列組成一個有序序列。重複第二步,直到全部組成一個有序序列。

歸併排序,是一種不穩定的排序是演算法這個演算法的時間複復雜度都是O(NlogN)

    //歸併排序,是一種不穩定的排序是演算法這個演算法的時間複復雜度都是O(NlogN)
    public void mergeSrot(int nums[],int start,int end){
        if(start>=end) return;
        int mid=(start+end)/2;
        mergeSrot(nums,start,mid);
        mergeSrot(nums,mid+1,end);
        mergeSrotHelper(nums,start,mid,end);
    }
        //  將兩個有序序列歸併為一個有序序列(二路歸併)
    private void mergeSrotHelper(int[] nums, int start, int mid, int end) {
        int[] arr=new int[end+1];
        int low=start;

        int left=start;
        int center=mid+1;

        while (left<=mid && center<=end){
            arr[low++] = nums[left] <= nums[center] ? nums[left++] : nums[center++];
        }

        while (left<=mid){
            arr[low++]=nums[left++];
        }
        while (center<=end){
            arr[low++]=nums[center++];
        }

        for(int i=start;i<=end;i++){
            nums[i]=arr[i];
        }
    }

堆排序

對簡單選擇排序的優化。

  1. 將序列構建成大頂堆。
  2. 將根節點與最後一個節點交換,然後斷開最後一個節點。
  3. 重複第一、二步,直到所有節點斷開。
  //堆排序
    private boolean isLeaf(int nums[],int pos)
    {
        //沒有葉子節點
        return pos*2+1>=nums.length;
    }

    private void swap(int[] nums,int pos1,int pos2)
    {
        int tmp;
        tmp=nums[pos2];
        nums[pos2]=nums[pos1];
        nums[pos1]=tmp;
    }

    private void shiftdown(int[] nums,int pos)
    {
        while(!isLeaf(nums,pos))
        {
            int left=pos*2+1;
            int right=pos*2+2;
            if(right<nums.length)
            {
                left=nums[left]>nums[right]?left:right;
            }
            //是否需要調整堆
            if(nums[pos]>=nums[left]) return;
            swap(nums,pos,left);
            pos=left;
        }
    }
    public void buildHeap(int nums[])
    {
        for(int i=nums.length/2-1;i>=0;i--)
        {
            shiftdown(nums,i);
        }
    }

    public void heapSort(int nums[])
    {
        for(int i=nums.length-1;i>=0;i--)
        {
            swap(nums,0,i);
            shiftdown(nums,i);
        }
    }

總結

一、穩定性:

  • 穩定:氣泡排序、插入排序、歸併排序和基數排序
  • 不穩定:選擇排序、快速排序、希爾排序、堆排序

二、平均時間複雜度

  • O(n^2):直接插入排序,簡單選擇排序,氣泡排序。

  • 在資料規模較小時(9W內),直接插入排序,簡單選擇排序差不多。當資料較大時,氣泡排序演算法的時間代價最高。效能為O(n^2)的演算法基本上是相鄰元素進行比較,基本上都是穩定的。

  • O(nlogn):快速排序,歸併排序,希爾排序,堆排序。

其中,快排是最好的,其次是歸併和希爾,堆排序在資料量很大時效果明顯。

三、排序演算法的選擇

  1. 資料規模較小
  • (1)待排序列基本序的情況下,可以選擇直接插入排序;

  • (2)對穩定性不作要求宜用簡單選擇排序,對穩定性有要求宜用插入或冒泡

  1. 資料規模不是很大
  • (1)完全可以用記憶體空間,序列雜亂無序,對穩定性沒有要求,快速排序,此時要付出log(N)的額外空間。

  • (2)序列本身可能有序,對穩定性有要求,空間允許下,宜用歸併排序

  1. 資料規模很大
  • (1)對穩定性有求,則可考慮歸併排序。

  • (2)對穩定性沒要求,宜用堆排序

  1. 序列初始基本有序(正序),宜用直接插入,冒泡

各演算法複雜度如下:

各大排序演算法總結