算法概述算法分类十种常见排序算法可以分为两大类:比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。非比较类排序:不通过比较来决定元素
算法概述
算法分类
十种常见排序算法可以分为两大类:
- 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
- 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
算法复杂度
相关概念
- 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
- 不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
- 时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
- 空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
冒泡排序(Bubble Sort)
动图演示
Java实现
/** * 冒泡排序 平均时间复杂度为o(n2),最好复杂度为o(n),空间复杂度为o(1) * * @author monkJay */public class BubbleSort { public static void solution(int[] array) { int len = array.length; // 从第一个开始直到倒数第二个 for (int i = 0; i < len - 1; ++i) { // 每比较一轮,都会有一个已排序好的数字,i就是已经排序好了的个数 for (int j = 1; j < len - i; ++j) { // 如果后面的数字小于前面的数字,则交换 if (array[j] < array[j - 1]) { int tmp = array[j]; array[j] = array[j - 1]; array[j - 1] = tmp; } } } } public static void main(String[] args) { int[] array = new int[] { 2, 1, 4, 3, 6, 5, 9, 8 }; solution(array); for (int i = 0; i < array.length; ++i) { System.out.print(array[i] + " "); } }}
选择排序(Selection Sort)
动图演示
Java实现
/** * 选择排序(最稳定的排序算法,不算什么序列时间复杂度都是o(n2)) 每次从未排序的序列中选择一个最小(大)的数放在已排序的序列的后面 * 平均时间复杂度为o(n2),最好复杂度为o(n2),空间复杂度为o(1) * * @author monkJay */public class SelectionSort { public static void solution(int[] array) { int len = array.length; for (int i = 0; i < len - 1; ++i) { int minIndex = i; for (int j = i + 1; j < len; ++j) { if (array[minIndex] > array[j]) { minIndex = j; } } int tmp = array[minIndex]; array[minIndex] = array[i]; array[i] = tmp; } }}
插入排序(Insertion Sort)
动图演示
Java实现
/** * 插入排序 | 选择未排序序列的第一个数,插入到已排序序列中合适得位置(从后往前找,直到找到一个比当前数小或者等于的数,插在它后面) * 平均时间复杂度为o(n2),最好复杂度为o(n),空间复杂度为o(1) * * @author monkJay */public class InsertionSort { public static void solution(int[] array) { int len = array.length; for (int i = 1; i < len; ++i) { int preIndex = i - 1; int current = array[i]; while (preIndex >= 0 && array[preIndex] > current) { // 如果有序序列中的数大于要插入的数,则后移一个位置(将自己的位置空出来) array[preIndex + 1] = array[preIndex]; // 继续往前寻找 --preIndex; } // 找到了适合的位置(终于找到一个小于等于要插入的数了),将数字插在这个位置后面 array[preIndex + 1] = current; } }}
希尔排序(Shell Sort)
动图演示
Java实现
/** * 希尔排序 将一个序列在逻辑上根据指定的步长分组,对每个分组进行插入排序(插入排序时的移动是根据当前步长移动查找的) * * @author monkJay */public class ShellSort { public static void solution(int[] array) { int len = array.length; // 步长从当前长度的一半开始,每一轮将步长减为一半,直到最后小于1结束 for (int gap = len / 2; gap > 0; gap /= 2) { // 取值从步长开始,这里其实就是将直接插入排序中的1全部换成了步长gap // 其它和直接插入排序一样 for (int i = gap; i < len; ++i) { int preIndex = i - gap; int current = array[i]; while (preIndex >= 0 && array[preIndex] > current) { array[preIndex + gap] = array[preIndex]; preIndex = preIndex - gap; } array[preIndex + gap] = current; } } }}
归并排序(Merge Sort)
动图演示
Java实现
/** * 二路归并排序 用到了分治的思想,不断往下细分,分到只有一个数字(认为是有序的)的时候开始合并,最后合并为一个有序的数组 * * @author monkJay */public class MergeSort { public static void solution(int[] array) { int len = array.length; if (len == 0) { return; } int left = 0; int right = len - 1; int[] tmp = new int[len]; mergeSort(array, tmp, left, right); } public static void mergeSort(int[] array, int[] tmp, int left, int right) { // 如果还可以分组,其实最后是分到只有一个数(认为一个数是有序的) if (left < right) { int center = left + (right - left) / 2; // 左边的数组继续往下分 mergeSort(array, tmp, left, center); // 右边的数组继续往下分 mergeSort(array, tmp, center + 1, right); // 将左右两个有序的数组合并 merge(array, tmp, left, center, right); } } public static void merge(int[] array, int[] tmp, int left, int center, int right) { int i = left, j = center + 1; // 将要合并的两个有序数组,从小到大有序的放在辅助数组中 for (int k = left; k <= right; ++k) { // 如果左边的数组遍历完了,就直接放右边的数组 if (i > center) { tmp[k] = array[j++]; } // 如果右边的数组遍历完了,就直接放左边的数组 else if (j > right) { tmp[k] = array[i++]; } // 将小的放在辅助数组中 else if (array[i] <= array[j]) { tmp[k] = array[i++]; } else { tmp[k] = array[j++]; } } // 将辅助数组中的值复制到原数组中 for (int k = left; k <= right; ++k) { array[k] = tmp[k]; } }}
快速排序(Quick Sort)
动图演示
Java实现
/** * 快速排序 * 通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小, * 则可分别对这两部分记录继续进行排序,以达到整个序列有序。 * @author monkJay */public class QuickSort { public static void solution(int[] array) { int len = array.length; if (len == 0) { return; } int left = 0, right = len - 1; quickSort(array, left, right); } public static void quickSort(int[] array, int left, int right) { if (left < right) { int partitionIndex = partition(array, left, right); quickSort(array, left, partitionIndex - 1); quickSort(array, partitionIndex + 1, right); } } // 第一种分区方法 public static int partition(int[] array, int left, int right) { // 选取左边第一个数作为中轴数 int pivot = left; // 从中轴数后面一个位置开始比较 int index = pivot + 1; // 保证指针左边的数都是小于中轴数的 for (int i = left + 1; i <= right; i++) { // 如果小于中轴数,将当前数的位置和中轴指针交换 if (array[i] < array[pivot]) { swap(array, i, index); // 指针继续后移 ++index; } } // 将中轴数的位置和指针左边一个位置交换,也就是把中轴数换到中间 swap(array, pivot, index - 1); // 最后返回这个中轴数的位置 return index - 1; } // 第二种分区方法 // public static int partition(int[] array, int left, int right) { // int i = left, j = right + 1; // int pivot = array[left]; // while (i < j) { // // while (array[--j] > pivot && i < j) // ; // while (array[++i] < pivot && i < j) // ; // if (i < j) { // swap(array, i, j); // } // } // swap(array, left, j); // return j; // } public static void swap(int[] array, int a, int b) { int tmp = array[a]; array[a] = array[b]; array[b] = tmp; }}
堆排序(Heap Sort)
动图演示
Java实现
/** * 堆排序 先构建一个最大(小)堆,每次都堆顶取一个数,并把最后一个数交换到堆顶,然后重新调整堆,重复,直到堆中最后一个数 * 最大堆得到从小到大的排序,最小堆得到从大到小的排序 * * @author HP * */public class HeapSort { public static void solution(int[] array) { int len = array.length; if (len == 0) { return; } buildHeap(array); heapSort(array); } /** * 下沉调整 用于堆的删除,还可用于构建二叉堆 * * @param array 存放堆的数组 * @param parentIndex 父结点位置 * @param len 数组长度 */ public static void downAdjust(int[] array, int parentIndex, int len) { // 先计算得到左孩子 int childIndex = parentIndex * 2 + 1; // 保存父结点的值 int tmp = array[parentIndex]; while (childIndex < len) { // 如果右孩子存在,且右孩子的值比左孩子要小,则将孩子节点定位到右孩子 if (childIndex + 1 < len && array[childIndex + 1] < array[childIndex]) { childIndex++; } // 如果父结点的值比两个孩子都小,不用调整了,直接结束 if (tmp < array[childIndex]) { break; } // 将孩子节点的值赋给父结点,继续下沉 array[parentIndex] = array[childIndex]; // 将孩子节点作为新的父结点 parentIndex = childIndex; // 重新计算左孩子的位置 childIndex = parentIndex * 2 + 1; } // 节点调整结束,将最初需要调整的那个节点赋值到最终位置 array[parentIndex] = tmp; } /** * 其实就是让每个非叶子节点依次下沉 * 构造堆 时间复杂度是线性的 * @param array 要构造堆的数组 */ public static void buildHeap(int[] array) { int len = array.length; // 找到最后一个节点的父结点 int startIndex = (len - 2) / 2; for (int i = startIndex; i >= 0; --i) { downAdjust(array, i, len); } } /** * 堆排序 每次都堆顶取一个数,并把最后一个数交换到堆顶,然后重新调整堆,重复,直到堆中最后一个数 * @param array */ public static void heapSort(int[] array) { for (int i = array.length - 1; i > 0; --i) { int tmp = array[0]; array[0] = array[i]; array[i] = tmp; // 从堆顶开始调整 downAdjust(array, 0, i); } }}
计数排序(Counting Sort)
动图演示
Java实现
/** * 计数排序 * 计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 * 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。 * @author HP */public class CountingSort { public static void solution(int[] array) { int len = array.length; if (len == 0) { return; } int maxValue = 0; // 得到最大值 for (int i = 0; i < len; ++i) { if (array[i] > maxValue) { maxValue = array[i]; } } int[] bucket = new int[maxValue + 1]; // 将桶用0填充 Arrays.fill(bucket, 0); // 记录每个元素出现的个数 for (int i = 0; i < len; ++i) { bucket[array[i]]++; } int index = 0; for (int i = 0; i < bucket.length; ++i) { // 将桶中的元素往原数组放 while (bucket[i] > 0) { array[index++] = i; bucket[i]--; } } }}
topK的四种解法
构造大顶堆
算法分析:用数组前k个数构造一个大顶堆,然后依次比较后面的数,如果小于堆顶,则换掉,重新调整堆,最后的堆顶元素就是第k大的元素。时间复杂度为O(klogk)。Java实现
public class TopK { public static int topK_Solution(int[] input, int k) { int len = input.length; if (len == 0 || k <= 0 || k > len) { return 0; } // 先把前k个数构造成一个堆 int[] array = Arrays.copyOfRange(input, 0, k); buildHeap(array); // 遍历后面的数,如果小于堆顶元素,就把堆顶换掉,并重新下沉调整堆 for (int i = k; i < len; ++i) { if (input[i] < array[0]) { array[0] = input[i]; downAdjust(array, 0, array.length); } } return array[0]; } // 构造堆 public static void buildHeap(int[] array) { int startIndex = (array.length - 2) / 2; for (int i = startIndex; i >= 0; --i) { downAdjust(array, i, array.length); } } // 调整最大堆 public static void downAdjust(int[] array, int parentIndex, int len) { int tmp = array[parentIndex]; int childIndex = parentIndex * 2 + 1; while (childIndex < len) { if (childIndex + 1 < len && array[childIndex + 1] > array[childIndex]) { childIndex++; } if (tmp > array[childIndex]) { break; } array[parentIndex] = array[childIndex]; parentIndex = childIndex; childIndex = parentIndex * 2 + 1; } array[parentIndex] = tmp; } public static void main(String[] args) { int[] array = new int[] { 2, 1, 4, 3, 6, 5, 7, 9, 8 }; int res = GetLeastNumbers_Solution(array, 5); System.out.print("第K大的元素是:" + res); }}
构造小顶堆
算法分析:先把数组构造成小顶堆,然后对堆进行k - 1次删除堆顶的操作,最后的堆顶就是要求的第k大元素 。时间复杂度为O(klogN)。
public class Solution { public int topK_Solution(int [] input, int k) { int len = input.length; if (len == 0 || k <= 0 || k > len){ return 0; } // 先构造一个堆 bulidHeap(input); // 将数组进行k - 1次删除堆顶操作 for (int i = len - 1; i > len - k; --i){ int tmp = input[0]; input[0] = input[i]; input[i] = tmp; downAdjust(input, 0, i); } // 返回最后的堆顶 return input[0]; } public void bulidHeap(int[] array){ int startIndex = (array.length - 2) / 2; for (int i = startIndex; i >= 0; --i){ downAdjust(array, i, array.length); } } public void downAdjust(int[] array, int parentIndex, int len){ int tmp = array[parentIndex]; int childIndex = parentIndex * 2 + 1; while (childIndex < len){ if (childIndex + 1 < len && array[childIndex + 1] < array[childIndex]){ childIndex ++; } if (tmp < array[childIndex]){ break; } array[parentIndex] = array[childIndex]; parentIndex = childIndex; childIndex = parentIndex * 2 + 1; } array[parentIndex] = tmp; }}
使用Java的优先队列
算法分析:其实跟前面用堆是一样的,只不过用了Java中已经封装好的堆,代码更简洁(但这里貌似耗时要长一点,其实这里删除堆顶不用调整,但它这里每次都调整了,所以浪费了时间)
public class Solution { public int topK_Solution(int [] input, int k) { int len = input.length; if (len == 0 || k <= 0 || k > len){ return 0; } // 使用优先队列构造一个最大堆,这里用了lambda表达式 PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, (o1, o2) -> { return o2.compareTo(o1); }); for (int i = 0; i < len; ++i) { // 往堆中添加数字,直到堆的大小为k if (maxHeap.size() != k) { maxHeap.offer(input[i]); } // 如果当前元素比堆顶的元素要小,那么将堆顶出队列,将当前元素加入队列(内部会自动调整堆) else if (input[i] < maxHeap.peek()) { maxHeap.poll(); maxHeap.offer(input[i]); } } // 返回堆顶元素 return maxHeap.peek(); }}
利用快排的思想
算法分析:基于数组的第k个数来调整,使得最后小于k的数都在k的左边,大于k的数都在k的右边,那么k就是第k大元素
public class Solution { public int topK_Solution(int [] input, int k) { int len = input.length; if (len == 0 || k <= 0 || k > len){ return 0; } int start = 0; int end = len - 1; int index = partition(input, start, end); while (index != k - 1){ if (index > k - 1){ end = index - 1; index = partition(input, start, end); } else { start = index + 1; index = partition(input, start, end); } } return input[k - 1]; } public int partition(int[] input, int left, int right){ int pivot = left; int index = pivot + 1; for (int i = left; i <= right; ++i){ if (input[i] < input[pivot]){ swap(input, i, index); ++index; } } swap(input, pivot, index - 1); return index - 1; } public void swap(int[] input, int a, int b){ int tmp = input[a]; input[a] = input[b]; input[b] = tmp; }}
. .

- 0