/** 快速排序前端
最坏O(N^2) 说明:当待排序的序列在排序前是有序(顺序或逆序)的,此时的时间复杂度最差(这种状况下,快排退化成了冒泡排序)。
平均O(NlogN)
最好O(NlogN)
*/ public class QuickSort {node
// ------- 1.数组的快速排序 ------- public int[] quickSort(int[] array, int left, int right) { if (left < right) { int pivotPosition = partition(array, left, right); // 获取基准元素的下标 quickSort(array, left, pivotPosition - 1); // 对基准元素的左边进行排序 quickSort(array, pivotPosition + 1, right); // 对基准元素的右边进行排序 } return array; } public int partition(int[] array, int left, int right) { int pivot = array[left]; // 选取数组的头元素做为基准元素 int pivotPosition = left; // 基准元素的索引 /** * 循环结束后: * 1)pivotPosition后面的元素都大于等于基准元素pivot * 2)pivotPosition前面的元素(包括pivotPosition指向的元素):除了array[left]外,其它的元素都小于基准元素pivot * 注:循环结束后,咱们还要将array[left]与array[pivotPosition]进行交换,以实现pivotposition前面的元素(不包括pivotPosition指向的元素)都小于基准元素 */ for (int i = pivotPosition + 1; i <= right; i++) { // i表示当前元素的索引 if (array[i] < pivot) { pivotPosition++; if (pivotPosition != i) { swap(array, pivotPosition, i); } } } /** * 目的: * 将pivotPosition指向基准元素pivot,而且返回pivotPosition,以肯定递归函数quickSort()中left参数与right参数的值 * 实现: * 交换基准元素与pivotPosition当前指向元素的位置。 */ if (pivotPosition != left) { array[left] = array[pivotPosition]; array[pivotPosition] = pivot; } return pivotPosition; } // ------- // ------- 2.链表的快速排序 ------- public void quickSort(Node left, Node right) { if (left == null || right == null || left == right) return; Node pivotPosition = partition(left, right); // 获取基准元素的下标 quickSort(left, pivotPosition); // 对基准元素的左边进行排序 quickSort(pivotPosition.next, right); // 对基准元素的右边进行排序 } public Node partition(Node left, Node right) { int pivot = left.value; // 选取链表的头元素做为基准元素 Node pivotPosition = left; // 基准元素的引用 for (Node i = pivotPosition.next; i != right.next; i = i.next) { // i表示当前元素的引用 if (i.value < pivot) { pivotPosition = pivotPosition.next; if (pivotPosition != i) { swap(pivotPosition, i); } } } if (pivotPosition != left) { left.value = pivotPosition.value; pivotPosition.value = pivot; } return pivotPosition; } // ------- /** * 数组中,交换两个元素的位置 */ private void swap(int[] array, int pivotPosition, int i) { int temp = array[pivotPosition]; array[pivotPosition] = array[i]; array[i] = temp; } /** * 链表中,交换两个元素的值 */ private void swap(Node pivotPosition, Node i) { int temp = pivotPosition.value; pivotPosition.value = i.value; i.value = temp; } public static void print(int[] array) { for (int i = 0; i < array.length; i++) { System.out.print(array[i] + ","); } System.out.println(); } public static void print(Node head) { ArrayList<Object> list = new ArrayList<>(); while (head.next != null) { list.add(head.value); head = head.next; } list.add(head.value); System.out.println(list.toString()); } public static void main(String[] args) { QuickSort qs = new QuickSort(); // test array quicksort int[] array = {2, 5, 9, 3, 17, 4, 18, 7, 6}; int[] sortArray = qs.quickSort(array, 0, array.length - 1); print(sortArray); // test linkedlist quicksort Node head = new Node(5); Node node1 = new Node(3); Node node2 = new Node(2); Node node3 = new Node(6); Node node4 = new Node(7); Node node5 = new Node(17); Node node6 = new Node(9); head.next = node1; node1.next = node2; node2.next = node3; node3.next = node4; node4.next = node5; node5.next = node6; qs.quickSort(head, node6); print(head); }
}算法
============================================================================数组
/**函数
最坏O(N^2) 说明:当待排序的序列在排序前是逆序的,此时的时间复杂度最差
平均O(N^2)
最好O(N) 说明:当待排序的序列在排序前是顺序的,此时的时间复杂度最好
*/ public class BubbleSort {测试
public static void sort(int[] array) { // 总共冒泡array.length-1次,每次冒泡都会将未排序序列中的最小元素放到未排序序列的最前端。 for (int i = 0; i < array.length-1; i++) { // 一趟冒泡(排序):从后往前比较相邻的元素,较小的元素往前冒泡,最终将最小的元素放到未排序序列的最前端。 for (int j = array.length - 1; j > 0; j--) { if (array[j] < array[j - 1]) { swap(array, j, j - 1); } } } } /** * 数组中,交换两个元素的位置 */ public static void swap(int[] array, int i, int j) { int temp = array[i]; array[i] = array[j]; array[j] = temp; } public static void main(String[] args) { int[] data = {2, 5, 9, 3, 17, 4, 18, 7, 6}; sort(data); System.out.println(Arrays.toString(data)); }
}优化
============================================================================ui
/**this
最坏O(N^2)
平均O(N^2)
最好O(N^2)
*/ public class SelectSort {.net
public static void sort(int[] array) { // 将array[i]和array[i+1 ~ a.length]中的最小元素进行交换 for (int i = 0; i < array.length - 1; i++) { // 记录本次循环中最小元素的下标,初始值为第一个元素的下标 int minIndex = i; // 将本次循环中最小值的下标赋值给minIndex for (int j = i + 1; j < array.length; j++) { if (array[j] < array[minIndex]) { minIndex = j; } } if (minIndex != i) { // 将本次(第i次)循环中的最小值a[minIndex]与a[i]进行交换 swap(array, minIndex, i); } } } /** * 数组中,交换两个元素的位置 */ public static void swap(int[] array, int i, int j) { int temp = array[i]; array[i] = array[j]; array[j] = temp; } public static void main(String[] args) { int[] data = {2, 5, 9, 3, 17, 4, 18, 7, 6}; sort(data); System.out.println(Arrays.toString(data)); }
}
============================================================================
/**
最坏O(N^2)
平均O(N^2)
最好O(N)
*/ public class InsertSort {
public static void sort(int[] array) { int insertElement; // 要插入的元素的值 int j; // 有序序列的下标 /** * 经过遍历前面的有序序列,将下标为i的元素插入到不大于本身的元素的后面。 */ for (int i = 1; i < array.length; i++) { // 将array[i]做为要插入的元素,从数组的第二个元素开始遍历。 insertElement = array[i]; // 要插入的元素 j = i - 1; // 有序序列最后一个元素的下标 (注:要插入的元素 前面的序列是有序序列,从后向前遍历前面的有序序列)。 while (j >= 0 && array[j] > insertElement) { // 1.若是下标为j的元素大于要插入的元素,则将下标为j的元素向后移动一位。 array[j + 1] = array[j]; j--; } array[j + 1] = insertElement; // 2.直到下标为j的元素不大于要插入的元素,则将要插入的元素插入到下标为j+1的位置。 } } public static void main(String[] args) { int[] data = {2, 5, 9, 3, 17, 4, 18, 7, 6}; sort(data); System.out.println(Arrays.toString(data)); }
}
============================================================================
/**
平均O(N^1.3)
*/ public class ShellSort {
public static void sort(int[] array) { if (array == null || array.length <= 1) { return; } int increment = array.length / 2; // 增量 while (increment > 0) { for (int i = 0; i < array.length; i++) { // 遍历待排序序列 for (int j = i; j+increment < array.length; j += increment) { // 对子序列进行插入排序 if (array[j] > array[j+increment]) { swap(array,j,j+increment); } } } increment = increment / 2; // 设置新的增量,进行下一轮的遍历。 } } /** * 数组中,交换两个元素的位置 */ public static void swap(int[] array, int i, int j) { int temp = array[i]; array[i] = array[j]; array[j] = temp; } public static void main(String[] args) { int[] data = {2, 5, 9, 3, 17, 4, 18, 7, 6}; sort(data); System.out.println(Arrays.toString(data)); }
}
============================================================================
/**
<p>
最坏O(NlogN)
平均O(NlogN)
最好O(NlogN)
*/ public class MergeSort {
public static int[] sort(int[] array, int low, int high) { if (low < high) { // 当low=high时,说明已经分解到单个元素了 int mid = (low + high) / 2; sort(array, low, mid); // 递归地对左半部分进行分解 sort(array, mid + 1, high); // 递归地对右半部分进行分解 merge(array, low, mid, high); // 分解已完成,将左子序列和右子序列进行合并 } return array; } /** * 将左子序列和右子序列合并为一个有序序列。 * * [@param](https://my.oschina.net/u/2303379) array * [@param](https://my.oschina.net/u/2303379) low * [@param](https://my.oschina.net/u/2303379) mid * [@param](https://my.oschina.net/u/2303379) high */ public static void merge(int[] array, int low, int mid, int high) { int[] temp = new int[high - low + 1]; // 临时数组,长度与原数组长度一致 int i = low; // 左边序列的第一个元素的下标 int j = mid + 1; // 右边序列的第一个元素的下标 int k = 0; // 临时数组temp的下标 // eg: 左边的序列为:6 9 13 右边的序列为:4 7 /** * 把较小的数先copy到临时数组temp中: ==> 依次将 四、六、7 copy到临时数组temp中。 */ while (i <= mid && j <= high) { if (array[i] < array[j]) { temp[k++] = array[i++]; } else { temp[k++] = array[j++]; } } // 若是左边的序列中还有未copy过的元素,则把左边剩余的元素copy临时数组temp中。 ==> 依次将 九、13 copy到临时数组temp中。 while (i <= mid) { temp[k++] = array[i++]; } // 若是右边的序列中还有未copy过的元素,则把右边剩余的元素copy临时数组temp中。 while (j <= high) { temp[k++] = array[j++]; } // 用排好序的临时数组temp覆盖原数组 for (int t = 0; t < temp.length; t++) { array[t + low] = temp[t]; } } public static void main(String[] args) { int[] data = {2, 5, 9, 3, 17, 4, 18, 7, 6}; sort(data,0,data.length-1); System.out.println(Arrays.toString(data)); }
}
============================================================================
/**
堆的概念:堆是一棵顺序存储的彻底二叉树。
堆分为大根堆和小根堆:
大根堆:每一个节点的值不小于等于其左、右孩子的值
小根堆:每一个节点的值不大于等于其左、右孩子的值
1)将一个待排序的序列构形成一个堆:从最后一个非叶子结点向上遍历,直到全部的非叶子节点都遍历完毕(筛选法)。
2)移走堆顶元素后,将剩余的元素再次构形成一个新的堆。
1)将待排序的n个元素构形成一个大根堆。
2)移走堆顶元素(即:将堆顶元素与堆数组的末尾元素进行交换,此时末尾元素为最大值)。
3)将剩余的n-1个元素从新构形成一个大根堆,重复上面的步骤,直到剩余的元素只有一个时,排序完成。
将一个彻底二叉树按层序排号依次存入数组:
8
/ \
3 9 ---按层序排号存入数组---> [8,3,9,5,7,4,2]
/ \ / \
5 7 4 2
1)下标为i的节点,其父节点的下标为(i-1)/2
2)下标为i的节点,其左子节点的下标为2*i+1,右子节点的下标为2*i+2
问题:top K
方案:
第1步)分治:先将全部的数据按照hash方法分解成多个较小数据集。
第2步)使用堆排序分别找出这几个较小数据集中的topK。
第3步)将第2步中各数据集的topk放在一个集合里,而后求出最终的topK。
1)时间复杂度:最好、最坏、平均 的复杂度都是 O(nlogn),故堆排序堆输入的数据不敏感。
建堆:O(n)
调整:O(log n)
2)空间复杂度:堆排序是就地排序,故其空间复杂度为O(1)。
*/ public class HeapSort {
public static int[] array; public static int adjustTime = 0; public static int whileTime = 0; public HeapSort(int[] array) { this.array = array; } /** * 根据子节点的索引来获取父节点的索引 * * [@param](https://my.oschina.net/u/2303379) child * @return */ public static int parentIndex(int child) { return (child - 1) / 2; } /** * 根据父节点的索引来获取左子节点的索引 * * @param parent * @return */ public static int leftChildIndex(int parent) { return parent * 2 + 1; } /** * 第一步:将待排序的n个元素构形成一个大根堆。 * * 将数组初始化为大根堆:从下(最后一个非叶子节点)往上(堆的根节点)循环遍历。 * * 要点:遍历存在左子节点的父节点,从最后一个非叶子结点开始遍历。 * 说明: * 1)彻底二叉树是按层序排号存入数组的,故二叉树的最后一个节点(即:数组中索引值最大的元素)必定是叶子节点,故最后一个节点必定有父节点,且最后一个节点的父节点就是 堆最后一个非叶子节点。 * 2)二叉树的最后一个节点的索引为array.length-1,则其父节点(即:最后一个非叶子节点)的索引为(array.length-1-1)/2,故咱们从array.length/2-1开始遍历! */ public static void initHeap() { // 从下往上的循环 for (int parentIndex = parentIndex(array.length-1); parentIndex >= 0; parentIndex--) { adjustHeap(array, parentIndex, array.length - 1); } } /** * 对堆进行排序 */ public static void sortHeap() { // array.length-1次 调整完成排序 for (int i = array.length - 1; i > 0; i--) { // 第二步:将堆顶元素(数组中第一个元素)和当前未排序子序列中的最后一个元素交换 swap(array, 0, i); // 第三步:交换后,将剩余的n-1个元素从新构形成一个大根堆 adjustHeap(array, 0, i-1); } } /** * 调整堆:从上往下循环遍历,即 沿父节点的较大子节点向下调整 * * @param array * @param parentIndex * @param maxIndex */ public static void adjustHeap(int[] array, int parentIndex, int maxIndex) { adjustTime++; int temp = array[parentIndex]; // 父节点的值 int child = leftChildIndex(parentIndex); // 左子节点的索引 // 从上往下循环:沿父节点的较大子节点向下调整 while (child <= maxIndex) { // 左子节点必须在未排序子序列中 whileTime++; // 记录循环的次数,测试用。 // 若当前节点(即:父节点)存在右子节点(且右子节点在未排序的子序列中),而且右子节点的值大于左子节点时,将右子节点的索引赋值给child if (child + 1 <= maxIndex && array[child + 1] > array[child]) { child++; // 将左子节点转换为右子节点 } // 此时,child表示 子节点中值最大的那个节点 的索引 // 若当前节点(即:父节点)的值大于子节点的值时,直接退出。 if (temp > array[child]) { break; } else { array[parentIndex] = array[child]; // 将子节点的值赋值给父节点 // 选取子节点的左子节点继续向下调整(执行wile循环) parentIndex = child; child = leftChildIndex(parentIndex); } } // 若发生了交换,则parentIndex表示子节点的索引;若没有发生交换,则parentIndex仍旧表示父节点的索引。 array[parentIndex] = temp; } public static void swap(int[] array, int i, int j) { int temp = array[i]; array[i] = array[j]; array[j] = temp; } public static void main(String[] args) { int[] array = {2, 13, 5, 7, 14, 6, 10, 8, 11, 4, 3, 9, 1, 12, 0}; HeapSort heapSort = new HeapSort(array); heapSort.initHeap(); heapSort.sortHeap(); System.out.println("排序后数组" + Arrays.toString(heapSort.array) + " 调整次数" + adjustTime + " while循环次数" + whileTime); }
}