排序专题(一) / 稳定的内部排序

本文深入探讨并实现了包括冒泡排序、桶排序、鸡尾酒排序、Gnome排序、插入排序、图书馆排序、归并排序和基数排序在内的多种排序算法,详细阐述了每种算法的思想、特点及代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 1.冒泡排序(Bubble Sort)

    • 思想:众所皆知,无需多言
    • 平均时间复杂度:O(n2)
    • public class BubleSort {    
          public static void bubleSort(int[] array) {        
              if (array.length <= 1) {
                  return;
              } else {
                  for (int i = 0; i < array.length - 1; i ++) {
                      for (int j = i; j < array.length; j ++) {
                          if (array[i] > array[j]) {
                              int temp = array[i];
                              array[i] = array[j];
                              array[j] = temp;
                          }
                      }
                  }
              }
          }
      }
  • 2.桶排序(Bucket Sort,或者称箱子排序)

    • 基于数据“分发-收集”模型
    • 算法描述:按照一定规则设置确定数量的“桶”或者称“箱子”,显然设置“桶”或者“箱子”的规则必须满足两个条件:
      • 数据经过你设计的规则必然能分发到某个“桶”中且只能唯一分发到这个桶中
      • 按照你设计的规则从各个“桶”中按一定次序收集出来的数据便是有序的
    • 平均时间复杂度:设上设置的“桶”个数为k,数据量为n,则为O(n+k)
    • 显然此算法已不基于传统的“比较”模型,故时间复杂度不受O(nlogn)下限的限制,当要排序的数据按照一定规则均匀分布时,算法时间复杂度逼近线性O(n)
    • public class BucketSort {    
          private static String appendZeroAtLeft(String str, int specifiedLength) {
              if (str.length() == specifiedLength) {
                  return str;
              } else {
                  StringBuffer sb = new StringBuffer();
                  if (str.length() < specifiedLength) {
                      for (int i = 0; i < specifiedLength - str.length(); i ++) {
                          sb.append("0");
                      }
                      sb.append(str);
                  }
                  return sb.toString();
              }
          }    
          public static void bucketSort(int[] array) {        
              if (array.length <= 1) {
                  return;
              } else {            
                  /*
                   *  Find the max and min value in this array, and after this for loop
                   *  the value of the variable "max" and "min" must be one value of this array.
                   */
                  int max = Integer.MIN_VALUE;
                  int min = Integer.MAX_VALUE;
                  for (int i = 0; i < array.length; i++) {
                      max = max > array[i] ? max : array[i];
                      min = min < array[i] ? min : array[i];
                  }            
                  /*
                   * Cover the condition that there are negative numbers in this array.
                   */
                  List<Integer> negativeNumberList = null;
                  boolean additionModificationTag = false;
                  if (min < 0) {
                      if (max + Math.abs(min) == 0) {
                          return;
                      } else if (max + Math.abs(min) < 0) {
                          negativeNumberList = new ArrayList<Integer>();
                      } else {
                          additionModificationTag = true;
                          for (int i = 0; i < array.length; i++) {
                              array[i] += Math.abs(min);
                          }
                      }
                  }            
                  /*
                   *  Create new buckets at most 256 + one negative numbers bucket.
                   */
                  Map<String, List<Integer>> bucketMap = 
                          new HashMap<String, List<Integer>>();
                  
                  for (int i = 0; i <= 255; i ++) {
                      String key = appendZeroAtLeft(Integer.toBinaryString(i << 24 >>> 24), 8);
                      bucketMap.put(key, new ArrayList<Integer>());
                  }            
                  /*
                   *  Distribute elements of the array to be sorted.
                   */
                  for (int i = 0; i < array.length; i++) {
                      if (array[i] < 0) {
                          if (negativeNumberList.size() == 0) {
                              negativeNumberList.add(array[i]);
                          } else {
                              for (int j = 0; j <= negativeNumberList.size(); j ++) {
                                  if (j == negativeNumberList.size()) {
                                      negativeNumberList.add(array[i]);
                                      break;
                                  }
                                  if (array[i] <= negativeNumberList.get(j)) {
                                      negativeNumberList.add(j, array[i]);
                                      break;
                                  }
                              }
                          }
                      } else {
                          String key = appendZeroAtLeft(
                                  Integer.toBinaryString(array[i] << 12 >>> 24), 8);
                          
                          if (bucketMap.get(key).size() == 0) {
                              bucketMap.get(key).add(array[i]);
                          } else {
                              for (int j = 0; j <= bucketMap.get(key).size(); j ++) {
                                  if (j == bucketMap.get(key).size()) {
                                      bucketMap.get(key).add(array[i]);
                                      break;
                                  }
                                  if (array[i] <= bucketMap.get(key).get(j)) {
                                      bucketMap.get(key).add(j, array[i]);
                                      break;
                                  }
                              }
                          }
                      }
                  }            
                  /*
                   *  Gather the elements dispersed in those different buckets.
                   */
                  for (int i = 0; i < array.length;) {
                      if (!additionModificationTag) {
                          for (Integer element : negativeNumberList) {
                              array[i] = element;
                              i ++;
                          }
                      }
                      for (int j = 0; j < bucketMap.size(); j ++) {
                          String key = appendZeroAtLeft(
                                  Integer.toBinaryString(j << 24 >>> 24), 8);
                          
                          if (bucketMap.get(key).size() != 0) {
                              for (Integer element : bucketMap.get(key)) {
                                  array[i] = element;
                                  i ++;
                              }
                          }
                      }
                  }            
                  /*
                   *  Restoration dispose.
                   */
                  if (additionModificationTag) {
                      for (int i = 0; i < array.length; i++) {
                          array[i] -= Math.abs(min);
                      }
                  }
              }        
          }
      }
  • 3.鸡尾酒排序(Cocktail Sort,双向的冒泡排序)

    • 平均时间复杂度:O(n2)
    • 此算法与冒泡排序的不同处在于排序时对序列双向冒泡,设置“交换标志位”,当不再发生交换时认为序列有序
    • public class CocktailSort {    
          public static void cocktailSort(int[] array) {        
              if (array.length <= 1) {
                  return;
              } else {
                  boolean flag = true;            
                  while (flag) {                
                      flag = false;                
                      int fromIndex = 0;
                      int toIndex = array.length - 1;                
                      for (int i = 0; i < toIndex; i ++) {
                          if (array[i] > array[i + 1]) {
                              flag = true;                        
                              array[i] ^= array[i + 1];
                              array[i + 1] ^= array[i];
                              array[i] ^= array[i + 1];
                          }
                      }
                      toIndex --;                
                      for (int j = toIndex; j > fromIndex; j --) {
                          if (array[j] < array[j - 1]) {
                              flag = true;                        
                              array[j] ^= array[j - 1];
                              array[j - 1] ^= array[j];
                              array[j] ^= array[j - 1];
                          }
                      }
                      fromIndex ++;
                  }
              }        
          }
      }
  • 4.Gnome Sort

    • 平均时间复杂度:O(n2)
    • 算法思想:每趟循环找到序列中第一个逆序的元素,把它和在它前面的已有序的元素逐个进行比较、交换(注意是交换,不是元素的依次后移),所以只是类似“插入排序”
    • 算法特点:代码极其精简
    • public class GnomeSort {    
          public static int[] gnomeSort(int[] array) {        
              int i = 0;        
              while (i < array.length) {            
                  if (i == 0 || array[i - 1] <= array[i]) {
                      i ++;
                  } else {
                      int temp = array[i - 1];
                      array[i - 1] = array[i];
                      array[i] = temp;                
                      i --;
                  }
              }        
              return array;
          }
      }
  • 5.插入排序(Insertion Sort)

    • 平均时间复杂度:O(n2)
    • 算法思想:以首元素为初始有序序列,依次取后面的待排序数据在有序序列中寻找它应该处于的位置,这样不断扩大有序序列,直到整个序列有序
    • 算法缺点很明显:可能发生数据的频繁移位
    • public class InsertionSort {    
          public static void insertionSort(int[] array) {        
              if (array.length <= 1) {
                  return;
              } else {
                  for (int i = 0, j = i; i < array.length - 1; j = ++ i) {
                      int temp = array[i + 1];
                      while (temp < array[j]) {
                          array[j + 1] = array[j];
                          if (j -- == 0) {
                              break;
                          }
                      }
                      array[j + 1] = temp;
                  }
              }        
          }    
      }
  • 6.图书馆排序(Library Sort)

    • 平均时间复杂度:O(nlogn)
    • 算法思想:排列图书时,如果在每本书之间留一定的空隙,那么在插入新书时就有可能会少移动一些书,说白了就是在插入排序的基础上,给元素与元素之间留一定的空隙,这个空隙越大,需要移动的书就会越少
    • 典型的以空间换时间的做法
    • 元素与元素之间的“空隙”没有理论上的最佳实践,依据具体情况而定
    • 图书馆排序的关键是分配空间,分配完空间后直接使用插入排序即可
    • public class LibrarySort {    
          private static final double FACTOR = Math.E;    
          private static List<Integer> dispenser(int[] array) {        
              List<Integer> longestSortedSequence = 
                      FindLongestSortedSequence.findLongestSortedSequence(array);
              
              int tempSize = longestSortedSequence.size();        
              int extendCapacity = (int) ((array.length - longestSortedSequence.size())
                      * FACTOR);
              
              int index = 0;
              int firstFillingQuantity = (int) (extendCapacity / FACTOR);
              for (int i = 0; i < firstFillingQuantity; i ++) {
                  longestSortedSequence.add(index, null);
              }
              index += firstFillingQuantity + 1;        
              int remainingPositions = extendCapacity - firstFillingQuantity;
              int remainder = remainingPositions % tempSize;
              int secondFillingQuantity = remainder + remainingPositions / tempSize;
              for (int i = 0; i < secondFillingQuantity; i++) {
                  longestSortedSequence.add(index, null);
              }
              index += secondFillingQuantity + 1;        
              int stepLength = (extendCapacity - firstFillingQuantity 
                      - secondFillingQuantity) / (tempSize - 2);
              
              for (int i = 0; i < tempSize - 2; i ++) {
                  for (int j = 0; j < stepLength; j ++) {
                      longestSortedSequence.add(index, null);
                  }
                  index += stepLength + 1;
              }        
              return longestSortedSequence;        
          }    
          public static int[] librarySort(int[] array) {        
              int[] resultArray = new int[array.length];        
              List<Integer> extendList = dispenser(array);        
              for (int i = 0; i < array.length; i++) {
                  if (!extendList.contains(array[i])) {
                      int j = 0;
                      while (j < extendList.size()) {
                          if (extendList.get(j) != null) {
                              if (extendList.get(j) >= array[i]) {
                                  if (extendList.get(j - 1) == null) {
                                      extendList.set(j - 1, array[i]);
                                      break;
                                  } else {
                                      extendList.add(j, array[i]);
                                      break;
                                  }
                              }
                          }
                          j ++;
                      }
                  }
              }        
              int index = 0;
              for (Integer element : extendList) {
                  if (element != null) {
                      resultArray[index ++] = element;
                  }
              }        
              return resultArray;
          }    
      }
  • 7.归并排序(Merge Sort,此处介绍2-路归并)

    • 平均时间复杂度:O(nlogn)
    • 建立在归并基础上的一种排序算法
    • 是“分治(Divide and Conquer)”思想的应用
    • 算法思想见图:
    • 2-路归并排序
    • public class MergeSort {    
          private static void merge(int[] array, int fromIndex, int splitIndex, int toIndex) {        
              int[] tempArray = new int[toIndex - fromIndex + 1];        
              int index = 0;        
              int i = fromIndex;
              int j = splitIndex + 1;        
              while (i <= splitIndex && j <= toIndex) {
                  if (array[i] < array[j]) {
                      tempArray[index ++] = array[i ++];
                  } else if (array[i] == array[j]) {
                      tempArray[index ++] = array[i ++];
                      tempArray[index ++] = array[j ++];
                  } else {
                      tempArray[index ++] = array[j ++];
                  }
              }        
              while (i <= splitIndex) {
                  tempArray[index ++] = array[i ++];
              }        
              while (j <= toIndex) {
                  tempArray[index ++] = array[j ++];
              }        
              for (int k = fromIndex, m = 0; k <= toIndex;) {
                  array[k ++] = tempArray[m ++];
              }        
          }    
          public static void mergeSort(int[] array, int fromIndex, int toIndex) {        
              int length = toIndex - fromIndex + 1;        
              if (length <= 1 || fromIndex < 0 || toIndex <= 0) {
                  return;
              }        
              int stepLength = 2;        
              while (length >= stepLength) {            
                  int startIndex = fromIndex;
                  int counter = 0;            
                  while ((length - counter * stepLength) >= stepLength) {                
                      merge(array, startIndex, startIndex + (stepLength - 1) / 2, 
                              startIndex + stepLength - 1);
                      
                      startIndex += stepLength;
                      counter ++;
                  }            
                  if ((length - counter * stepLength) > stepLength / 2) {
                      merge(array, counter * stepLength, 
                              counter * stepLength + stepLength / 2 - 1, toIndex);
                  }            
                  stepLength *= 2;            
              }        
              merge(array, fromIndex, fromIndex + stepLength / 2 - 1, toIndex);        
          }
      }
  • 8.基数排序(Radix Sort)

    • 个人认为可以看作多次的“桶排序”、“箱子排序”,即:多关键字,按照各个关键字对数据进行多次“分发-收集”
    • public class RadixSort {    
          private static Map<String, List<Integer>> buildSortingMap() {
              Map<String, List<Integer>> sortingMap= new HashMap<String, List<Integer>>();
              for (int i = 9; i >= -9; i --) {
                  if (i != 0) {
                      sortingMap.put(String.valueOf(i), new ArrayList<Integer>());
                  } else {
                      sortingMap.put("+0", new ArrayList<Integer>());
                      sortingMap.put("-0", new ArrayList<Integer>());
                  }
              }
              return sortingMap;
          }    
          private static int getLongestLength(int[] array) {
              int longestLength = 0;
              for (int i = 0; i < array.length; i++) {
                  int tempLength = String.valueOf(array[i]).length();
                  if (array[i] < 0) {
                      longestLength = tempLength - 1 > longestLength ? 
                              tempLength - 1 : longestLength; 
                  } else {
                      longestLength = tempLength > longestLength ? 
                              tempLength : longestLength;
                  }
              }
              return longestLength;
          }    
          private static void distributionCollection(int[] array, int which, 
                  Map<String, List<Integer>> sortingMap) {
              
              int[] tempArray = new int[array.length];
              int index = 0;        
              /*
               *  Distribution.
               */
              for (int i = 0; i < array.length; i++) {
                  String key = null;
                  int temp = 0;
                  if (which > 1) {
                      temp = (int) (array[i] % Math.pow(10, which) / Math.pow(10, which - 1));
                  } else {
                      temp = (int) (array[i] % Math.pow(10, which));
                  }
                  if (temp == 0) {
                      if (array[i] >= 0) {
                          key = "+0";
                      } else {
                          key = "-0";
                      }
                  } else {
                      key = String.valueOf(temp);
                  }
                  List<Integer> tempList = sortingMap.get(key);
                  if (tempList.size() == 0) {
                      tempList.add(array[i]);
                  } else {
                      for (int j = 0; j <= tempList.size(); j ++) {
                          if (j == tempList.size()) {
                              tempList.add(array[i]);
                              break;
                          }
                          if (array[i] < tempList.get(j)) {
                              tempList.add(j, array[i]);
                              break;
                          }
                      }
                  }
              }        
              /*
               *  Collection.
               */
              for (int k = -9; k <= 9; k ++) {
                  if (k != 0) {
                      for (Integer element : sortingMap.get(String.valueOf(k))) {
                          tempArray[index ++] = element;
                      }
                  } else {
                      for (Integer element : sortingMap.get("-0")) {
                          tempArray[index ++] = element;
                      }
                      for (Integer element : sortingMap.get("+0")) {
                          tempArray[index ++] = element;
                      }
                  }
              }        
              for (int j = 0; j < tempArray.length; j++) {
                  array[j] = tempArray[j];
              }        
          }
          
          public static int[] radixSort(int[] array) {        
              int longestLength = getLongestLength(array);        
              for (int i = 1; i <= longestLength; i ++) {
                  Map<String, List<Integer>> sortingMap = buildSortingMap();
                  distributionCollection(array, i, sortingMap);
              }        
              return array;
          }
      }

  • PS:
    • Code都是去年工作那半年闲暇时间写的,很大目的是为了弥补自己本科学习期间没动手写这些基础code的缺憾
    • 实现完全是按照个人对维基百科上算法的描述理解来的,有谬误之处望大家多指正
    • 所有code都经过UT测试
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值