乐闻技术笔记

经典排序算法

常用算法

经典排序算法

参考

算法总结
算法分类

  • 冒泡排序(Bubble Sort)

    
    public static int[] bubbleSort(int[] array) {
          if (array.length == 0)
              return array;
          for (int i = 0; i < array.length; i++)
              for (int j = 0; j < array.length - 1 - i; j++)
                  if (array[j + 1] < array[j]) {
                      int temp = array[j + 1];
                      array[j + 1] = array[j];
                      array[j] = temp;
                  }
          return array;
    
  • 选择排序(Selection Sort)

    
    public static int[] selectionSort(int[] array) {
         if (array.length == 0)
             return array;
         for (int i = 0; i < array.length; i++) {
             int minIndex = i;
             for (int j = i; j < array.length; j++) {
                 if (array[j] < array[minIndex]) //找到最小的数
                     minIndex = j; //将最小数的索引保存
             }
             int temp = array[minIndex];
             array[minIndex] = array[i];
             array[i] = temp;
         }
         return array;
     }
    

    工作原理: 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

  • 插入排序(Insertion Sort)

    
    public static int[] insertionSort(int[] array) {
        if (array.length == 0)
            return array;
        int current;
        for (int i = 0; i < array.length - 1; i++) {
            current = array[i + 1];
            int preIndex = i;
            while (preIndex >= 0 && current < array[preIndex]) {
                array[preIndex + 1] = array[preIndex];
                preIndex--;
            }
            array[preIndex + 1] = current;
        }
        return array;
    }
    

    一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

  • 希尔排序(Shell Sort)

    
    public static int[] ShellSort(int[] array) {
          int len = array.length;
          int temp, gap = len / 2;
          while (gap > 0) {
              for (int i = gap; i < len; i++) {
                  temp = array[i];
                  int preIndex = i - gap;
                  while (preIndex >= 0 && array[preIndex] > temp) {
                      array[preIndex + gap] = array[preIndex];
                      preIndex -= gap;
                  }
                  array[preIndex + gap] = temp;
              }
              gap /= 2;
          }
          return array;
      }
    

    选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2…1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。

  • 归并排序(Merge Sort)

public static int[] MergeSort(int[] array) {
    if (array.length < 2) return array;
    int mid = array.length / 2;
    int[] left = Arrays.copyOfRange(array, 0, mid);
    int[] right = Arrays.copyOfRange(array, mid, array.length);
    return merge(MergeSort(left), MergeSort(right));
}

public static int[] merge(int[] left, int[] right) {
    int[] result = new int[left.length + right.length];
    for (int index = 0, i = 0, j = 0; index < result.length; index++) {
        if (i >= left.length)
            result[index] = right[j++];
        else if (j >= right.length)
            result[index] = left[i++];
        else if (left[i] > right[j])
            result[index] = right[j++];
        else
            result[index] = left[i++];
    }
    return result;
}

把长度为n的输入序列分成两个长度为n/2的子序列;
对这两个子序列分别采用归并排序;
将两个排序好的子序列合并成一个最终的排序序列。

  • 快速排序(Quick Sort)

    
    public static int[] QuickSort(int[] array, int start, int end) {
          if (array.length < 1 || start < 0 || end >= array.length || start > end) return null;
          int smallIndex = partition(array, start, end);
          if (smallIndex > start)
              QuickSort(array, start, smallIndex - 1);
          if (smallIndex < end)
              QuickSort(array, smallIndex + 1, end);
          return array;
      }
    
      public static int partition(int[] array, int start, int end) {
          int pivot = (int) (start + Math.random() * (end - start + 1));
          int smallIndex = start - 1;
          swap(array, pivot, end);
          for (int i = start; i <= end; i++) if (array[i] <="array[end])" { smallindex++; (i> smallIndex)
                      swap(array, i, smallIndex);
              }
          return smallIndex;
      }
    
      public static void swap(int[] array, int i, int j) {
          int temp = array[i];
          array[i] = array[j];
          array[j] = temp;
      }
    

    快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
    从数列中挑出一个元素,称为 “基准”(pivot);
    重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
    递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

  • 堆排序(Heap Sort)

/**
     * 堆排序算法
     *
     * @param array
     * @return
     */

public static int[] HeapSort(int[] array) {
        len = array.length;
        if (len < 1) return array;
        //1.构建一个最大堆
        buildMaxHeap(array);
        //2.循环将堆首位(最大值)与末位交换,然后在重新调整最大堆
        while (len > 0) {
            swap(array, 0, len - 1);
            len--;
            adjustHeap(array, 0);
        }
        return array;
    }
    /**
     * 建立最大堆
     *
     * @param array
     */
    public static void buildMaxHeap(int[] array) {
        //从最后一个非叶子节点开始向上构造最大堆
        for (int i = (len/2 - 1); i >= 0; i--) { //感谢 @让我发会呆 网友的提醒,此处应该为 i = (len/2 - 1)
            adjustHeap(array, i);
        }
    }
    /**
     * 调整使之成为最大堆
     *
     * @param array
     * @param i
     */
    public static void adjustHeap(int[] array, int i) {
        int maxIndex = i;
        //如果有左子树,且左子树大于父节点,则将最大指针指向左子树
        if (i * 2 < len && array[i * 2] > array[maxIndex])
            maxIndex = i * 2;
        //如果有右子树,且右子树大于父节点,则将最大指针指向右子树
        if (i * 2 + 1 < len && array[i * 2 + 1] > array[maxIndex])
            maxIndex = i * 2 + 1;
        //如果父节点不是最大值,则将父节点与最大值交换,并且递归调整与父节点交换的位置。
        if (maxIndex != i) {
            swap(array, maxIndex, i);
            adjustHeap(array, maxIndex);
        }
    }

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

  • 基数排序(Counting Sort)

    
    public static int[] CountingSort(int[] array) {
          if (array.length == 0) return array;
          int bias, min = array[0], max = array[0];
          for (int i = 1; i < array.length; i++) {
              if (array[i] > max)
                  max = array[i];
              if (array[i] < min)
                  min = array[i];
          }
          bias = 0 - min;
          int[] bucket = new int[max - min + 1];
          Arrays.fill(bucket, 0);
          for (int i = 0; i < array.length; i++) {
              bucket[array[i] + bias]++;
          }
          int index = 0, i = 0;
          while (index < array.length) {
              if (bucket[i] != 0) {
                  array[index] = i - bias;
                  bucket[i]--;
                  index++;
              } else
                  i++;
          }
          return array;
      }
    

    找出待排序的数组中最大和最小的元素;
    统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
    对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
    反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。

    当输入的元素是n 个0到k之间的整数时,它的运行时间是 O(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。

  • 桶排序(Bucket Sort)

    
    public static ArrayList BucketSort(ArrayList array, int bucketSize) {
          if (array == null || array.size() < 2)
              return array;
          int max = array.get(0), min = array.get(0);
          // 找到最大值最小值
          for (int i = 0; i < array.size(); i++) {
              if (array.get(i) > max)
                  max = array.get(i);
              if (array.get(i) < min)
                  min = array.get(i);
          }
          int bucketCount = (max - min) / bucketSize + 1;
          ArrayList> bucketArr = new ArrayList<>(bucketCount);
          ArrayList resultArr = new ArrayList<>();
          for (int i = 0; i < bucketCount; i++) {
              bucketArr.add(new ArrayList());
          }
          for (int i = 0; i < array.size(); i++) {
              bucketArr.get((array.get(i) - min) / bucketSize).add(array.get(i));
          }
          for (int i = 0; i < bucketCount; i++) {
              if (bucketSize == 1) { // 如果带排序数组中有重复数字时  感谢 @见风任然是风 朋友指出错误
                  for (int j = 0; j < bucketArr.get(i).size(); j++)
                      resultArr.add(bucketArr.get(i).get(j));
              } else {
                  if (bucketCount == 1)
                      bucketSize--;
                  ArrayList temp = BucketSort(bucketArr.get(i), bucketSize);
                  for (int j = 0; j < temp.size(); j++)
                      resultArr.add(temp.get(j));
              }
          }
          return resultArr;
      }
    

    人为设置一个BucketSize,作为每个桶所能放置多少个不同数值(例如当BucketSize==5时,该桶可以存放{1,2,3,4,5}这几种数字,但是容量不限,即可以存放100个3);
    遍历输入数据,并且把数据一个一个放到对应的桶里去;
    对每个不是空的桶进行排序,可以使用其它排序方法,也可以递归使用桶排序;
    从不是空的桶里把排好序的数据拼接起来。
    注意,如果递归使用桶排序为各个桶排序,则当桶数量为1时要手动减小BucketSize增加下一循环桶的数量,否则会陷入死循环,导致内存溢出。

  • 基数排序(Radix Sort)

    
    public static int[] RadixSort(int[] array) {
          if (array == null || array.length < 2)
              return array;
          // 1.先算出最大数的位数;
          int max = array[0];
          for (int i = 1; i < array.length; i++) {
              max = Math.max(max, array[i]);
          }
          int maxDigit = 0;
          while (max != 0) {
              max /= 10;
              maxDigit++;
          }
          int mod = 10, div = 1;
          ArrayList> bucketList = new ArrayList>();
          for (int i = 0; i < 10; i++)
              bucketList.add(new ArrayList());
          for (int i = 0; i < maxDigit; i++, mod *= 10, div *= 10) {
              for (int j = 0; j < array.length; j++) {
                  int num = (array[j] % mod) / div;
                  bucketList.get(num).add(array[j]);
              }
              int index = 0;
              for (int j = 0; j < bucketList.size(); j++) {
                  for (int k = 0; k < bucketList.get(j).size(); k++)
                      array[index++] = bucketList.get(j).get(k);
                  bucketList.get(j).clear();
              }
          }
          return array;
      }
    

    基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。
    取得数组中的最大数,并取得位数;
    arr为原始数组,从最低位开始取每个位组成radix数组;
    对radix进行计数排序(利用计数排序适用于小范围数的特点);

打赏本文
二维码

9

文章

9

标签

 文章分类