排序算法

算法总结

image-20200227192937376.png

冒泡算法

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

  1. public static int[] bubbleSort(int[] array) {
  2. if (array.length == 0) {
  3. return array;
  4. }
  5. for (int i = 0; i < array.length; i++) {
  6. for (int j = 0; j < array.length - 1 - i; j++) {
  7. if (array[j + 1] < array[j]) {
  8. int temp = array[j + 1];
  9. array[j + 1] = array[j];
  10. array[j] = temp;
  11. }
  12. }
  13. }
  14. return array;
  15. }

基础算法 - 图2

选择排序

表现最稳定的排序算法之一,因为无论什么数据进去都是O(n2)的时间复杂度,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。理论上讲,选择排序可能也是平时排序一般人想到的最多的排序方法了吧。
选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

  1. public static int[] selectSort(int[] array) {
  2. if (array.length == 0) {
  3. return array;
  4. }
  5. for (int i = 0; i < array.length; i++) {
  6. int minIndex = i;
  7. for (int j = i; j < array.length; j++) {
  8. if (array[j] < array[minIndex]) {
  9. minIndex = j;
  10. }
  11. }
  12. if (i != minIndex) {
  13. int temp = array[minIndex];
  14. array[minIndex] = array[i];
  15. array[i] = temp;
  16. }
  17. }
  18. return array;
  19. }

基础算法 - 图3

插入排序

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

  1. public static int[] insertSort(int[] array) {
  2. if (array.length == 0) {
  3. return array;
  4. }
  5. int current;
  6. for (int i = 0; i < array.length - 1; i++) {
  7. current = array[i + 1];
  8. int preIndex = i;
  9. while (preIndex >= 0 && current < array[preIndex]) {
  10. array[preIndex + 1] = array[preIndex];
  11. preIndex--;
  12. }
  13. array[preIndex + 1] = current;
  14. }
  15. return array;
  16. }

基础算法 - 图4

希尔排序

希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序

  1. public static int[] shellSort(int[] array) {
  2. int len = array.length;
  3. int temp, gap = len / 2;
  4. while (gap > 0) {
  5. for (int i = gap; i < len; i++) {
  6. temp = array[i];
  7. int preIndex = i - gap;
  8. while (preIndex >= 0 && array[preIndex] > temp) {
  9. array[preIndex + gap] = array[preIndex];
  10. preIndex -= gap;
  11. }
  12. array[preIndex + gap] = temp;
  13. }
  14. gap /= 2;
  15. }
  16. return array;
  17. }

归并排序

和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

  1. public static int[] mergeSort(int[] array) {
  2. if (array.length < 2) {
  3. return array;
  4. }
  5. int mid = array.length / 2;
  6. int[] left = Arrays.copyOfRange(array, 0, mid);
  7. int[] right = Arrays.copyOfRange(array, mid, array.length);
  8. return merge(mergeSort(left), mergeSort(right));
  9. }
  10. private static int[] merge(int[] left, int[] right) {
  11. int[] result = new int[left.length + right.length];
  12. for (int index = 0, i = 0, j = 0; index < result.length; index++) {
  13. if (i >= left.length) {
  14. result[index] = right[j++];
  15. } else if (j >= right.length) {
  16. result[index] = left[i++];
  17. } else if (left[i] > right[j]) {
  18. result[index] = right[j++];
  19. } else {
  20. result[index] = left[i++];
  21. }
  22. }
  23. return result;
  24. }

基础算法 - 图5

快速排序

快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

  1. private static void quickSort(int[] arr, int leftIndex, int rightIndex) {
  2. if (rightIndex <= leftIndex) {
  3. return;
  4. }
  5. int left = leftIndex;
  6. int right = rightIndex;
  7. int temp = arr[left];
  8. while (left < right) {
  9. while (right > left && arr[right] >= temp) {
  10. right--;
  11. }
  12. arr[left] = arr[right];
  13. while (left < right && arr[left] <= temp) {
  14. left++;
  15. }
  16. arr[right] = arr[left];
  17. }
  18. arr[right] = temp;
  19. quickSort(arr, leftIndex, left - 1);
  20. quickSort(arr, right + 1, rightIndex);
  21. }

基础算法 - 图6

堆排序

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

  1. public void heapSort(int[] list) {
  2. // 构造初始堆,从第一个非叶子节点开始调整,左右孩子节点中较大的交换到父节点中,堆顶是最大的元素
  3. for (int i = (list.length) / 2 - 1; i >= 0; i--) {
  4. heapAdjust(list, list.length, i);
  5. }
  6. // 排序,将最大的结点放在堆尾,然后从根结点重新调整
  7. for (int i = list.length - 1; i >= 1; i--) {
  8. int temp = list[0];
  9. list[0] = list[i];
  10. list[i] = temp;
  11. heapAdjust(list, i, 0);
  12. }
  13. }
  14. /**
  15. * @param list list
  16. * @param len 调整范围
  17. * @param i 父节点
  18. */
  19. private void heapAdjust(int[] list, int len, int i) {
  20. int k = i, temp = list[i], index = 2 * k + 1;
  21. while (index < len) {
  22. if (index + 1 < len) {
  23. if (list[index] < list[index + 1]) {
  24. index = index + 1;
  25. }
  26. }
  27. if (list[index] > temp) {
  28. list[k] = list[index];
  29. k = index;
  30. index = 2 * k + 1;
  31. } else {
  32. break;
  33. }
  34. }
  35. list[k] = temp;
  36. }

基础算法 - 图7

计数排序

计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
计数排序(Counting sort)是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。

  1. public static int[] countSort(int[] array) {
  2. if (array.length == 0) {
  3. return array;
  4. }
  5. int bias, min = array[0], max = array[0];
  6. for (int i = 1; i < array.length; i++) {
  7. if (array[i] > max) {
  8. max = array[i];
  9. }
  10. if (array[i] < min) {
  11. min = array[i];
  12. }
  13. }
  14. bias = -min;
  15. int[] bucket = new int[max - min + 1];
  16. Arrays.fill(bucket, 0);
  17. for (int i = 0; i < array.length; i++) {
  18. bucket[array[i] + bias]++;
  19. }
  20. int index = 0, i = 0;
  21. while (index < array.length) {
  22. if (bucket[i] != 0) {
  23. array[index] = i - bias;
  24. bucket[i]--;
  25. index++;
  26. } else {
  27. i++;
  28. }
  29. }
  30. return array;
  31. }

基础算法 - 图8

桶排序

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。
桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序。

  1. public static ArrayList<Integer> bucketSort(ArrayList<Integer> arrayList, int bucketSize) {
  2. if (arrayList == null || arrayList.size() < 2) {
  3. return arrayList;
  4. }
  5. int max = arrayList.get(0), min = arrayList.get(0);
  6. for (int i = 0; i < arrayList.size(); i++) {
  7. if (arrayList.get(i) > max) {
  8. max = arrayList.get(i);
  9. }
  10. if (arrayList.get(i) < min) {
  11. min = arrayList.get(i);
  12. }
  13. }
  14. int bucketCount = (max - min) / bucketSize + 1;
  15. ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketCount);
  16. ArrayList<Integer> resultArr = new ArrayList<>();
  17. for (int i = 0; i < bucketCount; i++) {
  18. bucketArr.add(new ArrayList<>());
  19. }
  20. for (int i = 0; i < arrayList.size(); i++) {
  21. bucketArr.get((arrayList.get(i) - min) / bucketSize).add(arrayList.get(i));
  22. }
  23. for (int i = 0; i < bucketCount; i++) {
  24. if (bucketSize == 1) {
  25. // 如果待排序数组中有重复数字时
  26. for (int j = 0; j < bucketArr.get(i).size(); j++) {
  27. resultArr.add(bucketArr.get(i).get(j));
  28. }
  29. } else {
  30. if (bucketCount == 1) {
  31. bucketSize--;
  32. }
  33. ArrayList<Integer> temp = bucketSort(bucketArr.get(i), bucketSize);
  34. for (int j = 0; j < temp.size(); j++) {
  35. resultArr.add(temp.get(j));
  36. }
  37. }
  38. }
  39. return resultArr;
  40. }

基数排序

基数排序也是非比较的排序算法,对每一位进行排序,从最低位开始排序,复杂度为O(kn),为数组长度,k为数组中的数的最大的位数。
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。

  1. /**
  2. * radix表示基数(进制),digits表示最大数值位数
  3. */
  4. public static int[] radixSort(int[] data, int radix, int digits) {
  5. LinkedList<LinkedList> queue = new LinkedList<>();
  6. for (int r = 0; r < radix; r++) {
  7. LinkedList<Integer> queue1 = new LinkedList<>();
  8. //offer用于queue中(LinkedList同时实现了List和queue接口),add用于List中
  9. queue.offer(queue1);
  10. }
  11. //最大元素的位数,进行digits次分配和收集
  12. for (int i = 0; i < digits; i++) {
  13. //分配数组元素
  14. for (int j = 0; j < data.length; j++) {
  15. //得到digits的第i+1位
  16. int r = (int) (data[j] % Math.pow(radix, i + 1) / Math.pow(radix, i));
  17. LinkedList<Integer> queue2 = queue.get(r);
  18. queue2.offer(data[j]);
  19. queue.set(r, queue2);
  20. }
  21. //将收集队列元素
  22. int count = 0;
  23. for (int k = 0; k < radix; k++) {
  24. while (queue.get(k).size() > 0) {
  25. data[count++] = (Integer) queue.get(k).poll();
  26. }
  27. }
  28. }
  29. return data;
  30. }

基础算法 - 图9

基数排序 vs 计数排序 vs 桶排序

这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

  • 基数排序:根据键值的每位数字来分配桶
  • 计数排序:每个桶只存储单一键值
  • 桶排序:每个桶存储一定范围的数值

    遍历

    ```java /**
    • 递归先序遍历
    • @param biTree 二叉树 */ public static void preOrderRe(TreeNode biTree) { System.out.println(biTree.value); if (biTree.left != null) {
      1. preOrderRe(biTree.left);
      } if (biTree.right != null) {
      1. preOrderRe(biTree.right);
      } }

/**

  • 非递归先序遍历 */ public static void preOrder(TreeNode biTree) { Stack stack = new Stack<>(); while (biTree != null || !stack.isEmpty()) {
    1. while (biTree != null) {
    2. System.out.println(biTree.value);
    3. stack.push(biTree);
    4. biTree = biTree.left;
    5. }
    6. if (!stack.isEmpty()) {
    7. biTree = stack.pop();
    8. biTree = biTree.right;
    9. }
    } }

/**

  • 非递归中序遍历 */ public static void inOrder(TreeNode biTree) { Stack stack = new Stack<>(); while (biTree != null || !stack.isEmpty()) {
    1. while (biTree != null) {
    2. stack.push(biTree);
    3. biTree = biTree.left;
    4. }
    5. if (!stack.isEmpty()) {
    6. biTree = stack.pop();
    7. System.out.println(biTree.value);
    8. biTree = biTree.right;
    9. }
    } }

/**

  • 后序非递归遍历 */ public static void postOrder(TreeNode biTree) { int left = 1; int right = 2; Stack stack = new Stack<>(); // 用来判断子节点返回父节点时处于左节点还是右节点 Stack stack2 = new Stack<>(); while (biTree != null || !stack.isEmpty()) {
    1. while (biTree != null) {
    2. //将节点压入栈1,并在栈2将节点标记为左节点
    3. stack.push(biTree);
    4. stack2.push(left);
    5. biTree = biTree.left;
    6. }
    7. while (!stack.isEmpty() && stack2.peek() == right) {
    8. //如果是从右子节点返回父节点,则任务完成,将两个栈的栈顶弹出
    9. stack2.pop();
    10. System.out.println(stack.pop().value);
    11. }
    12. if (!stack.isEmpty() && stack2.peek() == left) {
    13. stack2.pop();
    14. stack2.push(right);
    15. biTree = stack.peek().right;
    16. }
    } }

/**

  • 层次遍历
  • 和广度优先遍历一样,都需要使用队列。首先将头结点放入队列,接下来每次从队列的头部取出一个结点,
  • 遍历这个结点之后到达的结点,直到队列中的结点全部被遍历为止。 */ private static List> levelOrder(TreeNode biTree) { if (biTree == null) {
    1. return result;
    } Queue queue = new LinkedList<>(); queue.add(biTree); while (!queue.isEmpty()) {
    1. int count = queue.size();
    2. List<Integer> list = new ArrayList<>();
    3. while (count > 0) {
    4. TreeNode temp = queue.peek();
    5. // 弹出
    6. queue.poll();
    7. assert temp != null;
    8. list.add(temp.value);
    9. if (temp.left != null) {
    10. queue.add(temp.left);
    11. }
    12. if (temp.right != null) {
    13. queue.add(temp.right);
    14. }
    15. count--;
    16. }
    17. result.add(list);
    } return result; }
    1. <a name="fUDqk"></a>
    2. ## 进制转换
    3. <a name="Us1X5"></a>
    4. ### 十六进制转十进制
    5. ```java
    6. public int getTen(String input) {
    7. char[] chars = input.toCharArray();
    8. int j=0;
    9. int sum = 0;
    10. List<Character> tens =Arrays.asList('A', 'B', 'C', 'D', 'E', 'F');
    11. for (int i = chars.length - 1; i >= 0; i--) {
    12. int weight = 1;
    13. for (int z = 0; z < j; z++) {
    14. weight *= 16;
    15. }
    16. int base = 0;
    17. if (tens.contains(chars[i])) {
    18. base = chars[i] - 'A' + 10;
    19. } else {
    20. base = chars[i] - '0';
    21. }
    22. sum += base * weight;
    23. j++;
    24. }
    25. return sum;
    26. }

    十进制转十六进制

    1. public String getSixteen(Integer ten) {
    2. StringBuffer s = new StringBuffer();
    3. String a;
    4. char[] b = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
    5. while (ten != 0) {
    6. s = s.append(b[ten % 16]);
    7. ten = ten / 16;
    8. }
    9. a = s.reverse().toString();
    10. return a;
    11. }

    十进制转二进制

    1. public String getTwo(Integer ten) {
    2. StringBuffer s = new StringBuffer();
    3. while (ten != 0) {
    4. s = s.append(ten % 2);
    5. ten = ten / 2;
    6. }
    7. return s.reverse().toString();
    8. }

    二进制转十进制

    1. public Integer getTenFromTwo(String two) {
    2. char[] chars = two.toCharArray();
    3. int sum = 0;
    4. int j = 0;
    5. for (int i = chars.length - 1; i >= 0; i--) {
    6. int weight = 1;
    7. for (int z = 0; z < j; z++) {
    8. weight *= 2;
    9. }
    10. sum += (chars[i] - '0') * weight;
    11. j++;
    12. }
    13. return sum;
    14. }