1. 插入排序
    将第i个元素插入前i-1个已排好序的子序列中,使得前i个元素都是有序的,直到所有元素都是有序的。

    void insertSort(int[] nums) {
       for(int i=2; i<nums.length; i++) {
           int cur = nums[i];
           int j=i-1;
           for(; j>=0 && nums[j]>cur; j--) {
               nums[j+1] = nums[j];
           }
           nums[j+1] = cur;
       }
    }

    改进:二分插入排序 —— 在插入第 i 个元素时使用二分法在前 i-1 个元素中查找插入位置。

    void binaryInsertSort(int[] nums) {
        for(int i=1; i<nums.length; i++) {
            int cur = nums[i];
            int low = 0, high = i-1;
            while(low<=high) {
                int mid = low + (high-low)/2;
                if(cur>nums[mid]) high = mid - 1;
                else              low  = mid + 1;
            }
            for(int j=i-1; j>high; j--) {
                nums[j+1] = nums[j];
            }
            nums[high+1] = cur;
        }
    }
    
  2. 希尔排序
    缩小增量的插入排序:先将待排数组分割成若干子序列分别进行插入排序,待整个数组中的元素基本有序时再对整个数组进行一次直接插入排序。

     void shellSort(int[] nums, int delt[]) { // delt 为增量序列
         for(int k=0; k<delt.length; k++) {
             for(int i=delt[k]+1; i<nums.length; i=i+delt[k]) {
                 int j=i-delt[k];
                 for(; j>=0 && nums[i]<nums[j]; j=j-delt[k]) {
                     nums[j+delt[k]] = nums[j];
                 }
                 nums[j+delt[k]] = nums[i];
             }
         }
     }
    
  3. 冒泡排序
    第一趟冒泡排序:将数组的第1个和第2个元素比较,若为逆序则交换,继续比较第2个和第3个元素,直至第n-1和第n个元素比较为止,使得最大元素在数组末尾位置;
    第二趟冒泡排序:将数组的前n-1个元素进行同样操作,使得次大元素在数组倒数第二的位置;
    ...
    第i趟冒泡排序:将数组的前n-i+1个元素依次比较相邻元素,并在逆序时交换,使得这n-i+1个元素中最大的元素被交换到第n-i+1的位置上;
    ...
    直到进行 n-1 趟排序为止。

     void bubbleSort(int[] nums) {
         for(int i=0; i<nums.length-1; i++) {
             for(int j=0; j<nums.length-i-1; j++) {
                 if(nums[j]>nums[j+1]) {
                     int temp = nums[j+1];
                     nums[j+1] = nums[j];
                     nums[j] = temp;
                 }
             }
         }
     }
    
  4. 快速排序
    通过一趟排序将待排数组分割成独立的两部分,使得其中一部分元素比另一部分元素小,则可分别对这两部分元素继续进行排序,以达到整个数组有序。
    一趟快速排序的步骤:设置两个指针low, high, 分别指向待排序列的起止位置,设pivot为枢轴元素,暂存,首先从high所指位置起向前搜索找到第一个小于pivot的元素,然后从low所指位置向后搜索找到第一个大于pivot的元素,排序结束后将枢轴元素移至正确位置上。

    int partion(int[] nums, int low, int high) {
        int pivotKey = nums[low];
        while(low<high) {
            while(low<high && nums[high]>=pivotKey) high--;
            nums[low]=nums[high];
            while(low<high && nums[low]<=pivotKey)  low++;
            nums[high]=nums[low];
        }
        nums[low]=pivotKey;
        return low;
    }
    void quickSort(int[] nums, int low, int high) {
        if(low<high) {
            int index = partion(nums, low, high);
            quickSort(nums, low, index-1);
            quickSort(nums, index+1, high);
        }
    }

    切分函数:先在数组中选择一个数字,再把数组中数字分为两部分,比选择的数字小的移到数组的左边,大的移到数组的右边。

    int partionEx(int[] nums, int low, int high) {
        int pivot = low;
        swap(nums, pivot, high);
        int small = low-1;
        for(int i=low; i<high; i++) {
            if(nums[i]<nums[high]) {
                small++;
                if(small!=index) {
                    swap(nums, small, index);
                }
            }
        }
        small++;
        swap(nums, small, high);
        return small;
    }

    改进:三向切分——对于含有大量重复元素的数组可以将数组切分为三部分,即大于、等于和小于枢轴元素。

    void quickSortThreeWay(int[] nums, int low, int high) {
        if(low>=high) return;
        int pivotKey = nums[low], less=low, i = low+1, greater = high;
        while(i<=greater) {
            if(nums[i]<pivotKey) swap(nums, less++, i++);
            else if(nums[i]>pivotKey) swap(nums, i, greater--);
            else i++;
        }
        quickSortThreeWay(nums, low, less-1);
        quickSortThreeWay(nums, greater+1, high);
    }
  5. 选择排序
    第i趟排序中通过n-i次比较从子序列 [i+1,...n] 中选择最小的元素并与第i个元素交换。

     void selectSort(int[] nums) {
         for(int i=0; i<nums.length-1; i++) {
             int minPos = i;
             for(int j=i+1; j<nums.length; j++) {
                 if(nums[minPos]>nums[j]) {
                     minPos = j;
                 }
             }
             if(minPos!=i) swap(nums, i, minPos);
         }
     }
     
  6. 堆排序
    将数组视为一个完全二叉树:大顶堆中非叶节点元素 ≥ 其左右孩子,堆顶是最大值;小顶堆中非叶节点元素 ≤ 其左右孩子,堆顶是最小值。
    先将数组建成大顶堆,将堆顶元素与数组最后一个元素交换,然后对前n-1个元素进行筛选重新调整成一个大顶堆,如此反复直至数组有序。

     void heapAdjust(int[] nums, int low, int high) {
         int cur = nums[low];
         for(int i=2*low; i<=high; i=i*2) {
             if(i+1<=high && nums[i]<nums[i+1]) i++; // 孩子中的最大元素
             if(cur>=nums[i]) break;
             nums[low] = nums[i]; low = i;
         }
         nums[low] = cur;
     }
     void heapSort(int[] nums) {
         for(int i=nums.length/2; i>=0; i--) {
             heapAdjust(nums, i, nums.lengh-1);
         }
         for(int i=nums.length-1; i>0; i--) {
             swap(nums, 0, i);
             heapAdjust(nums, 0, i-1);
         }
     }
     
  7. 归并排序
    将多个有序序列合并成一个新的有序序列。

     void mergeSort(int[] nums, int low, int high) {
         if(low<high) {
             int mid = low+(high-low)/2;
             mergeSort(nums, low, mid);
             mergeSort(nums, mid+1, high);
             merge(nums, low, mid, high);
         }
     }
     void merge(int[] nums, int low, int mid, int high) {
         int[] temp = new int[high-low+1];
         int lowHF = low, highHF = mid+1, i=0;
         while(lowHF<=mid && highHF<=high) {
             if(nums[lowHF]<=nums[highHF]) temp[i++]=nums[lowHF++];
             else                          temp[i++]=nums[highHF++];
         }
         while(lowHF<=mid) {
             temp[i++]=nums[lowHF++];
         }
         while(highHF<=high) {
             temp[i++]=nums[highHF++];
         }
         for(i=0; i<temp.length; i++) {
             nums[i+low] = temp[i];
         }
     }
     
  8. 排序算法的比较

    冒泡排序:稳定; 时间复杂度 n^2; 空间复杂度 1.
    插入排序:稳定; 时间复杂度 n ~ n^2, 和初始顺序有关; 空间复杂度 1. 
    希尔排序:不稳定; 时间复杂度 n 的若干倍乘以递增序列的长度; 空间复杂度 1.    
    选择排序:不稳定; 时间复杂度 n^2; 空间复杂度 1.    
    快速排序:不稳定; 时间复杂度 nlogn ~ n^2; 空间复杂度 logn.
    三向切分快速排序:不稳定; 时间复杂度 n ~ nlogn; 空间复杂度 logn; 适用于有大量重复主键.
    堆排序:不稳定; 时间复杂度 nlogn; 空间复杂度 1.    
    归并排序:稳定; 时间复杂度 nlogn; 空间复杂度 N.

    快速排序是最快的通用排序算法,它的内循环的指令很少,而且它还能利用缓存,因为它总是顺序地访问数据。它的运行时间近似为 ~cNlogN,这里的 c 比其他线性对数级别的排序算法都要小。
    使用三向切分快速排序,实际应用中可能出现的某些分布的输入能够达到线性级别,而其它排序算法仍然需要线性对数时间。

  9. Java 的排序算法实现
    Java 主要排序方法为 java.util.Arrays.sort(),对于原始数据类型使用三向切分的快速排序,对于引用类型使用归并排序。


KitorinZero
7 声望1 粉丝

« 上一篇
数组查找
下一篇 »
图 Graph