排序
Bubble Sort 冒泡排序
定义:重复比较数组中相邻的元素,如果前一个元素比后一个元素大,则交换他们的位置,如此重复,直到将最大的元素放到最后;重复上述步骤,每一次都将前面未排序的最大元素已经放到最后;
助记码:
|
|
代码实现:
JAVA:
|
|
Python:
|
|
时间复杂度:({\displaystyle O(n^{2})})
我的理解:比较的次数越多,说明未排序的元素越少,所以第一次比较,比较的元素最多,交换的次数也是最多;当比较了 n - 2
次之后,只有最前面的元素没有排序,很显然这时已经完成排序操作啦。
Select Sort 选择排序
定义:找出数组中最小的元素,将它和数组的第一个元素交换位置,再次,在剩下的元素中找到最小的元素,将它和数组的第二个元素交换位置。
公共方法:
|
|
代码实现:
JAVA 实现:
|
|
Python 实现:
|
|
自己的理解:
- 在数组中找到最小的元素,如何实现呢? 将数组中的元素两两进行比较,确定最小元素的索引;
- 将最小的元素放到数组的起始位置;如何实现呢? 将最小元素的索引与第一个元素进行交换;
- 重复上述步骤;
Insert Sort 插入排序
定义:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
具体算法描述如下:
- 从第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素,在已经排序的元素序列中从后向前扫描
- 如果该元素(已排序)大于新元素,将该元素移到下一位置
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
- 将新元素插入到该位置后
- 重复步骤2~5
代码实现:
|
|
实战写法:
|
|
WIKI 写法:
|
|
算法的痛点:在遍历的同时也在执行交换操作。
改进:提前终止内层循环。针对近乎有序的数组,排序效率更高。
算法改进:
利用多次交换相邻元素减少元素的赋值操作
123456789101112131415161718192021public void insertSort(T arr[], int N) {for(int i = 1; i < N; i++) {T e = arr[i];int j;// j 保存元素 e 应该插入的位置for (j = i; j > 0 && arr[j - 1] > e; j--) {arr[j] = arr[j - 1];}arr[j] = e;}}public void insertSort(T arr[], int l, int r) {for (int i = l + 1; i <= r; i++) {T e = arr[i];int j;for(j = i; j > l && arr[j - 1] > e; j--) {arr[j] = arr[j - 1];}arr[j] = e;}}
InsertX
先将大元素移动到最后,减少交换的元素。12345678910111213141516171819202122232425262728293031/*** Rearranges the array in ascending order, using the natural order.* @param a the array to be sorted*/public static void sort(Comparable[] a) {int n = a.length;// put smallest element in position to serve as sentinelint exchanges = 0;for (int i = n-1; i > 0; i--) {if (less(a[i], a[i-1])) {exch(a, i, i-1);exchanges++;}}if (exchanges == 0) return;// insertion sort with half-exchangesfor (int i = 2; i < n; i++) {Comparable v = a[i];int j = i;while (less(v, a[j-1])) {a[j] = a[j-1];j--;}a[j] = v;}assert isSorted(a);}BinaryInsertion
二分插入123456789101112131415161718192021222324252627282930public class BinaryInsertion {// This class should not be instantiated.private BinaryInsertion() { }/*** Rearranges the array in ascending order, using the natural order.* @param a the array to be sorted*/public static void sort(Comparable[] a) {int n = a.length;for (int i = 1; i < n; i++) {// binary search to determine index j at which to insert a[i]Comparable v = a[i];int lo = 0, hi = i;while (lo < hi) {int mid = lo + (hi - lo) / 2;if (less(v, a[mid])) hi = mid;else lo = mid + 1;}// insetion sort with "half exchanges"// (insert a[i] at index j and shift a[j], ..., a[i-1] to right)for (int j = i; j > lo; --j)a[j] = a[j-1];a[lo] = v;}assert isSorted(a);}我的理解:在已知序列中从后向前扫描,对于未知元素找到合适的位置插入,同时将已知序列的元素向后移动一位。
Shell Sort 希尔排序
定义:使数组中任意间隔为 h 的元素都是有序的。
希尔排序是对插入排序的改进;插入元素是相邻元素进行移动,希尔排序是交换不相邻元素以对数组的局部进行排序,并最终利用插入排序将局部有序的数组进行排序。
代码实现:
|
|
WIKI 实现:
|
|
优化:
|
|
我的理解:将数组分为步长为 h 的序列,分别对他们进行插入排序;同时不断降低 步长 h 的幅度,在对数组进行插入排序,最后进行到步长为 1 时,说明数组已经排好序啦。
Merge Sort 归并排序
定义:先递归的将数组分成两半分别排序,然后将结果归并起来。体现的是 “分治” 的思想。
思想:指的是将两个已经排序的序列合并成一个序列的操作;
|
|
递归法(Top-down)
- 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
- 设定两个指针,最初位置分别为两个已经排序序列的起始位置
- 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
- 重复步骤3直到某一指针到达序列尾
- 将另一序列剩下的所有元素直接复制到合并序列尾
迭代法(Bottom-up)
原理如下(假设序列共有 { n} n个元素):
- 将序列每相邻两个数字进行归并操作,形成 ceil(n/2) 个序列,排序后每个序列包含两/一个元素
- 若此时序列数不是1个则将上述序列再次归并,形成 ceil(n/4)个序列,每个序列包含四/三个元素
- 重复步骤2,直到所有元素排序完毕,即序列数为1
代码实现:
|
|
归并排序算法的几种优化:
原地归并
1234567891011121314151617181920212223// stably merge a[lo .. mid] with a[mid+1 ..hi] using aux[lo .. hi]private static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) {// precondition: a[lo .. mid] and a[mid+1 .. hi] are sorted subarraysassert isSorted(a, lo, mid);assert isSorted(a, mid+1, hi);// copy to aux[]for (int k = lo; k <= hi; k++) {aux[k] = a[k];}// merge back to a[]int i = lo, j = mid+1;for (int k = lo; k <= hi; k++) {if (i > mid) a[k] = aux[j++];else if (j > hi) a[k] = aux[i++];else if (less(aux[j], aux[i])) a[k] = aux[j++];else a[k] = aux[i++];}// postcondition: a[lo .. hi] is sortedassert isSorted(a, lo, hi);}
自顶向下的归并排序
12345678// mergesort a[lo..hi] using auxiliary array aux[lo..hi]private static void sort(Comparable[] a, Comparable[] aux, int lo, int hi) {if (hi <= lo) return;int mid = lo + (hi - lo) / 2;sort(a, aux, lo, mid);sort(a, aux, mid + 1, hi);merge(a, aux, lo, mid, hi);}自底向上的归并排序: 以2个元素为最小单位,依次递增到全部元素的一半结束;可以针对链表进行排序
1234567891011121314151617181920212223242526272829//迭代/*** Rearranges the array in ascending order, using the natural order.* @param a the array to be sorted*/public static void sort(Comparable[] a) {int n = a.length;Comparable[] aux = new Comparable[n];//遍历元素for (int len = 1; len < n; len *= 2) {//要归并的元素的起始位置for (int lo = 0; lo < n-len; lo += len+len) {int mid = lo+len-1;int hi = Math.min(lo+len+len-1, n-1);merge(a, aux, lo, mid, hi);}}assert isSorted(a);}//改良版(同上)public void mergeSortBU(T arr[], int n) {for (int sz = 1; sz <= n; sz += sz) {for (int i = 0; i + sz < n; i += sz + sz) {//对 arr[i ... i + len - 1] 和 arr[i + len ... i + 2 * len - 1]进行归并merge(arr, i, i + sz - 1, Math.min(i + sz + sz -1, n - 1));}}}
我的理解:归并排序的思想是将数组复制一份,将复制出来的数组分为两半,将前后两部分中每一个元素进行比较,将较小的元素放到原数组中。
Quick Sort 快速排序
定义:首先找到 “切分” 元素,将数组分为两部分,将左右两部分分别排序,当两个数组都是有序时整个数组就是有序的。
JAVA 实现:
|
|
切分
随意取 a[0] 作为切分元素
从数组的左边开始向右扫描直到找到一个大于等于它的元素
再从数组的右边向左扫描直到找到一个小于等于它的元素
交换它们的位置。
如此继续
…
我们只需要将切分元素 a[0] 和左子数组最右侧的元素a[j] 交换,然后返回 j 即可。
随机快速排序:
123456789101112131415161718192021222324252627282930// partition the subarray a[lo..hi] so that a[lo..j-1] <= a[j] <= a[j+1..hi]// and return the index j.private static int partition(Comparable[] a, int lo, int hi) {int i = lo;int j = hi + 1;Comparable v = a[lo];while (true) {// find item on lo to swapwhile (less(a[++i], v)) {if (i == hi) break;}// find item on hi to swapwhile (less(v, a[--j])) {if (j == lo) break; // redundant since a[lo] acts as sentinel}// check if pointers crossif (i >= j) break;exch(a, i, j);}// put partitioning item v at a[j]exch(a, lo, j);// now, a[lo .. j-1] <= a[j] <= a[j+1 .. hi]return j;}切分代码实现:
123456789101112131415161718// 对arr[l...r]部分进行partition操作// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]//下面这个写法针对于:j 表示大于 V 的元素的索引,i 表示小于 V 元素的索引,当i 小于 V 时,将 j++,并将交换swap(arr[j], arr[i])元素。public int partition(T arr[], int l, int r) {T v = arr[l];//分界点int j = l;// i 是当前访问的元素for (int i = l + 1; i <= r; i++) {if (arr[i] < v) {swap(arr[j+1], arr[i]);j++;}}//最后切换 V 与 arr[j] 的位置,将 V 放在中间位置,左边是小于它的元素,右边是大于它的元素swap(arr[l], arr[j]);return j;}当数据较少时切换到插入排序
1234567891011121314151617// cutoff to insertion sort, must be >= 1private static final int INSERTION_SORT_CUTOFF = 8;// quicksort the subarray from a[lo] to a[hi]private static void sort(Comparable[] a, int lo, int hi) {if (hi <= lo) return;// cutoff to insertion sort (Insertion.sort() uses half-open intervals)int n = hi - lo + 1;if (n <= INSERTION_SORT_CUTOFF) {Insertion.sort(a, lo, hi + 1);return;}int j = partition(a, lo, hi);sort(a, lo, j-1);sort(a, j+1, hi);}双路快速排序
123456789101112131415public int partition(T arr, int l, int r) {T v = arr[l];// arr[l + 1 ... i] <= v; arr[j...r] >= v;int i = l + 1, j = r;while(true) {while(i <= r && arr[i] < v) i++;while(j >= l + 1 && arr[j] > v) j--;if (i > j) break;swap(arr[i], arr[j]);i++;j--;}swap(arr[l], arr[j]);return j;}
三路快速排序算法: 适用处理大量重复元素
第一种方式
123456789101112131415161718// quicksort the subarray a[lo .. hi] using 3-way partitioningprivate static void sort(Comparable[] a, int lo, int hi) {if (hi <= lo) return;int lt = lo, gt = hi;Comparable v = a[lo];int i = lo + 1;while (i <= gt) {int cmp = a[i].compareTo(v);if (cmp < 0) exch(a, lt++, i++);else if (cmp > 0) exch(a, i, gt--);else i++;}// a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi].sort(a, lo, lt-1);sort(a, gt+1, hi);assert isSorted(a, lo, hi);}第二种方式
12345678910111213141516171819202122public void quickSort3Way(T arr[], int l, int r){T v = arr[l];int lt = l;// arr[l+1 ...lt] < vint gt = r + 1; // arr[gt ...r] > vint i = l + 1; // arr[lt+1 ... i] == vwhile(i < gt) {if (arr[i] < v) {swap(arr[i], arr[lt+1]);lt++;i++;} else if (arr[i] > v) {swap(arr[i], arr[gt - 1]);gt--;} else {i++;}}swap(arr[l], arr[lt]);quickSort3Way(arr, l, lt - 1);quickSort3Way(arr, gt, r);}
用非递归方式进行快速排序
用 Stack 存储数组的下标
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889package sort.algorithm;import java.util.Stack;//快速排序的非递归实现,利用系统的栈stackpublic class QuickSortNonRecursion {public static void main(String[] args) {QuickSortNonRecursion qsnr = new QuickSortNonRecursion();int[] array = {0, 2, 11, 121, 18, 99, 3, 5, 101, 22, 9, 100};qsnr.quicksort(array);for (int i : array) {System.out.print(i + " ");}}public void quicksort(int[] array) {if (array == null || array.length == 1) return;//存放开始与结束索引Stack<Integer> s = new Stack<Integer>();//压栈s.push(0);s.push(array.length - 1);//利用循环里实现while (!s.empty()) {int right = s.pop();int left = s.pop();//如果最大索引小于等于左边索引,说明结束了if (right <= left) continue;int i = partition(array, left, right);if (left < i - 1) {s.push(left);s.push(i - 1);}if (i + 1 < right) {s.push(i+1);s.push(right);}}}//找到轴心,进行交换public int partition (int[] data, int first, int end){int temp;int i=first,j=end;if(first<end){temp=data[i];//当i=j的时候,则说明扫描完成了while(i<j){//从右边向左边扫描找到一个小于temp的元素while(j>i&&data[j]>temp)j--;if(i<j){//将该元素赋值给tempdata[i]=data[j];//赋值后就应该将i+1指向下一个序号i++;}//然后从左边向右边开始扫描,找到一个大于temp的元素while(i<j&&temp>data[i])i++;if(i<j){//将该元素赋值给tempdata[j]=data[i];//赋值后就应该将j-1指向前一个序号j--;}}//将轴数据放在i位置中data[i]=temp;}return i;}}private static int partition(int[] a, int start, int end) {int pivot = a[start];while (start < end) {while (start < end && a[end] >= pivot)end--;a[start] = a[end];while (start < end && a[start] <= pivot)start++;a[end] = a[start];}a[start] = pivot;return start;}
我的理解:快速排序的思想是 选取一个基准元素,一般是第一个元素 arr[0],从数组的左边开始向右扫描,找到第一个比 基准元素大的元素的索引 i
,同时在数组的末尾从右向左扫描找到第一个比数组小的元素索引 j
,交换他们的位置,如此重复,最后将基准元素与右侧指针所指向的元素arr[j]
交换位置,至此我们就确定了切分元素的在数组的位置。递归处理从起始点到 j - 1
的元素,和处理从j + 1
到 len
(数组长度)的元素。
Heap Sort 堆排序
二叉堆: 是一组能够用堆有序的完全二叉树排序的元素,并在数组中按照层级存储(不使用数组的第一个元素)。
二叉堆是一颗完全二叉树;用数组存储二叉堆。
最大堆: 堆中某一个节点的值总是不大于其父结点的值;
最大堆
上浮:因为子结点变得比父结点大导致堆的有序状态被破坏,那么通过交换它和它的父结点来修复堆。
123456private void swim(int k) {while (k > 1 && less(k/2, k)) {exch(k, k/2);k = k/2;}}下沉:将它与它的两个子结点中的较大者交换来恢复堆的有序状态;
123456789private void sink(int k) {while (2*k <= n) {int j = 2*k;if (j < n && less(j, j+1)) j++;if (!less(k, j)) break;exch(k, j);k = j;}}基于堆的优先队列
JAVA 实现:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247public class MaxPQ<Key> implements Iterable<Key> {private Key[] pq; // store items at indices 1 to nprivate int n; // number of items on priority queueprivate Comparator<Key> comparator; // optional comparator/*** Initializes an empty priority queue with the given initial capacity.** @param initCapacity the initial capacity of this priority queue*/public MaxPQ(int initCapacity) {pq = (Key[]) new Object[initCapacity + 1];n = 0;}/*** Initializes an empty priority queue.*/public MaxPQ() {this(1);}/*** Initializes an empty priority queue with the given initial capacity,* using the given comparator.** @param initCapacity the initial capacity of this priority queue* @param comparator the order in which to compare the keys*/public MaxPQ(int initCapacity, Comparator<Key> comparator) {this.comparator = comparator;pq = (Key[]) new Object[initCapacity + 1];n = 0;}/*** Initializes an empty priority queue using the given comparator.** @param comparator the order in which to compare the keys*/public MaxPQ(Comparator<Key> comparator) {this(1, comparator);}/*** Initializes a priority queue from the array of keys.* Takes time proportional to the number of keys, using sink-based heap construction.** @param keys the array of keys*/public MaxPQ(Key[] keys) {n = keys.length;pq = (Key[]) new Object[keys.length + 1];for (int i = 0; i < n; i++)pq[i+1] = keys[i];//构造堆for (int k = n/2; k >= 1; k--)sink(k);assert isMaxHeap();}/*** Returns true if this priority queue is empty.** @return {@code true} if this priority queue is empty;* {@code false} otherwise*/public boolean isEmpty() {return n == 0;}/*** Returns the number of keys on this priority queue.** @return the number of keys on this priority queue*/public int size() {return n;}/*** Returns a largest key on this priority queue.** @return a largest key on this priority queue* @throws NoSuchElementException if this priority queue is empty*/public Key max() {if (isEmpty()) throw new NoSuchElementException("Priority queue underflow");return pq[1];}// helper function to double the size of the heap arrayprivate void resize(int capacity) {assert capacity > n;Key[] temp = (Key[]) new Object[capacity];for (int i = 1; i <= n; i++) {temp[i] = pq[i];}pq = temp;}/*** Adds a new key to this priority queue.** @param x the new key to add to this priority queue*/public void insert(Key x) {// double size of array if necessaryif (n == pq.length - 1) resize(2 * pq.length);// add x, and percolate it up to maintain heap invariantpq[++n] = x;swim(n);assert isMaxHeap();}/*** Removes and returns a largest key on this priority queue.** @return a largest key on this priority queue* @throws NoSuchElementException if this priority queue is empty*/public Key delMax() {if (isEmpty()) throw new NoSuchElementException("Priority queue underflow");Key max = pq[1];exch(1, n--);sink(1);pq[n+1] = null; // to avoid loiterig and help with garbage collectionif ((n > 0) && (n == (pq.length - 1) / 4)) resize(pq.length / 2);assert isMaxHeap();return max;}/**************************************************************************** Helper functions to restore the heap invariant.***************************************************************************/private void swim(int k) {while (k > 1 && less(k/2, k)) {exch(k, k/2);k = k/2;}}private void sink(int k) {while (2*k <= n) {int j = 2*k;if (j < n && less(j, j+1)) j++;if (!less(k, j)) break;exch(k, j);k = j;}}/**************************************************************************** Helper functions for compares and swaps.***************************************************************************/private boolean less(int i, int j) {if (comparator == null) {return ((Comparable<Key>) pq[i]).compareTo(pq[j]) < 0;}else {return comparator.compare(pq[i], pq[j]) < 0;}}private void exch(int i, int j) {Key swap = pq[i];pq[i] = pq[j];pq[j] = swap;}// is pq[1..N] a max heap?private boolean isMaxHeap() {return isMaxHeap(1);}// is subtree of pq[1..n] rooted at k a max heap?private boolean isMaxHeap(int k) {if (k > n) return true;int left = 2*k;int right = 2*k + 1;if (left <= n && less(k, left)) return false;if (right <= n && less(k, right)) return false;return isMaxHeap(left) && isMaxHeap(right);}/**************************************************************************** Iterator.***************************************************************************//*** Returns an iterator that iterates over the keys on this priority queue* in descending order.* The iterator doesn't implement {@code remove()} since it's optional.** @return an iterator that iterates over the keys in descending order*/public Iterator<Key> iterator() {return new HeapIterator();}private class HeapIterator implements Iterator<Key> {// create a new pqprivate MaxPQ<Key> copy;// add all items to copy of heap// takes linear time since already in heap order so no keys movepublic HeapIterator() {if (comparator == null) copy = new MaxPQ<Key>(size());else copy = new MaxPQ<Key>(size(), comparator);for (int i = 1; i <= n; i++)copy.insert(pq[i]);}public boolean hasNext() { return !copy.isEmpty(); }public void remove() { throw new UnsupportedOperationException(); }public Key next() {if (!hasNext()) throw new NoSuchElementException();return copy.delMax();}}/*** Unit tests the {@code MaxPQ} data type.** @param args the command-line arguments*/public static void main(String[] args) {MaxPQ<String> pq = new MaxPQ<String>();while (!StdIn.isEmpty()) {String item = StdIn.readString();if (!item.equals("-")) pq.insert(item);else if (!pq.isEmpty()) StdOut.print(pq.delMax() + " ");}StdOut.println("(" + pq.size() + " left on pq)");}}
堆排序
堆的构造阶段,从第一个不是叶子结点的位置开始下沉操作。
聪明的做法:从右至左构造用
sink()
函数构造子堆,将数组中的每个位置都看成是一个子堆的根结点,那么在该结点上调用sink()
可以将它们变成一个堆,这个过程会递归地建立起堆的秩序,开始时我们只需要扫描数组中的一半元素,因为我们可以跳过大小为1的子堆,最后在位置为1 上调用sink()
方法,扫描结束。下沉排序阶段 , 从 堆中按递减顺序取出所有元素并得到排序结果;
本质是将堆中的最大元素删除,然后放入堆缩小后数组中空出的位置。
堆的构造
12345678910public MaxPQ(Key[] keys) {n = keys.length;pq = (Key[]) new Object[keys.length + 1];for (int i = 0; i < n; i++)pq[i+1] = keys[i];//构造堆for (int k = n/2; k >= 1; k--)sink(k);assert isMaxHeap();}
堆的下沉排序
12345678910public static void sort(Comparable[] pq) {int n = pq.length;for (int k = n/2; k >= 1; k--)sink(pq, k, n);//下沉while (n > 1) {exch(pq, 1, n--);sink(pq, 1, n);}}堆的实现:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566public class Heap {// This class should not be instantiated.private Heap() { }/*** Rearranges the array in ascending order, using the natural order.* @param pq the array to be sorted*/public static void sort(Comparable[] pq) {int n = pq.length;for (int k = n/2; k >= 1; k--)sink(pq, k, n);while (n > 1) {exch(pq, 1, n--);sink(pq, 1, n);}}/**************************************************************************** Helper functions to restore the heap invariant.***************************************************************************/private static void sink(Comparable[] pq, int k, int n) {while (2*k <= n) {int j = 2*k;if (j < n && less(pq, j, j+1)) j++;if (!less(pq, k, j)) break;exch(pq, k, j);k = j;}}/**************************************************************************** Helper functions for comparisons and swaps.* Indices are "off-by-one" to support 1-based indexing.***************************************************************************/private static boolean less(Comparable[] pq, int i, int j) {return pq[i-1].compareTo(pq[j-1]) < 0;}private static void exch(Object[] pq, int i, int j) {Object swap = pq[i-1];pq[i-1] = pq[j-1];pq[j-1] = swap;}// print array to standard outputprivate static void show(Comparable[] a) {for (int i = 0; i < a.length; i++) {StdOut.println(a[i]);}}/*** Reads in a sequence of strings from standard input; heapsorts them;* and prints them to standard output in ascending order.** @param args the command-line arguments*/public static void main(String[] args) {String[] a = StdIn.readAllStrings();Heap.sort(a);show(a);}}
最小堆
|
|
索引堆
最大索引堆
|
|
最小索引堆
|
|
分治算法
Merge Sort & Quick Sort
逆序对
取数组中第N大的元素
时间复杂度 O(N)
具体题目
题目:请实现一个排序算法,要求时间效率为O(n).(允许使用常量大小的辅助空间不得超过O(n))
该题来自于剑指offer,用空间来换时间效率
|
|