- 最好情况:每次划分选取的基准都是当前无序区的中值。如果每次划分过程产生的区间大小都为n/2,则快速排序法运行就快得多了。
public void sort(int[] arr, int low, int high) {
int l = low;
int h = high;
int povit = arr[low];
while (l < h) {
while (l < h && arr[h] >= povit)
h–;
if (l < h) {
arr[l] = arr[h];
l++;
}
while (l < h && arr[l] <= povit)
l++;
if (l < h) {
arr[h] = arr[l];
h–;
}
}
arr[l] = povit;
System.out.print(“l=” + (l + 1) + “;h=” + (h + 1) + “;povit=” + povit + “\n”);
System.out.println(Arrays.toString(arr));
if (l - 1 > low) sort(arr, low, l - 1);
if (h + 1 < high) sort(arr, h + 1, high);
}
######快排的优化
- 当待排序序列的长度分割到一定大小后,使用插入排序。
- 快排函数在函数尾部有两次递归操作,我们可以对其使用尾递归优化。优化后,可以缩减堆栈深度,由原来的O(n)缩减为O(logn),将会提高性能。
- 从左、中、右三个数中取中间值。
#####6.4插入排序
直接插入排序
插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。是稳定的排序方法。 插入算法把要排序的数组分成两部分:第一部分包含了这个数组的所有元素,但将最后一个元素除外(让数组多一个空间才有插入的位置),而第二部分就只包含这一个元素(即待插入元素)。在第一部分排序完成后,再将这个最后元素插入到已排好序的第一部分中。
void insert_sort(int* a, int len) {
for (int i = 1; i < len; ++i) {
int j = i - 1;
int temp = a[i];
while (j >= 0 && temp < a[j]) {
a[j + 1] = a[j];
j–;
}
a[j + 1] = temp;
}
}
######希尔排序
也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
void shell_sort(int* a, int len) {
int step = len / 2;
int temp;
while (step > 0) {
for (int i = step; i < len; ++i) {
temp = a[i];
int j = i - step;
while (j >= 0 && temp < a[j]) {
a[j + step] = a[j];
j -= step;
}
a[j + step] = temp;
}
step /= 2;
}
}
#####6.5选择排序
######直接选择排序
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。实际适用的场合非常罕见。
void selection_sort(int arr[], int len) {
int i, j, min, temp;
for (i = 0; i < len - 1; i++) {
min = i;
for (j = i + 1; j < len; j++)
if (arr[min] > arr[j])
min = j;
temp = arr[min];
arr[min] = arr[i];
arr[i] = temp;
}
}
#####6.6堆排序
堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。
- 将数组分为有序区和无序区,在无序区中建立最大堆
- 将堆顶的数据与无序区末尾的数据交换
- 从后往前,直到所有数据排序完成
public void heapSort(int[] nums) {
for (int i = nums.length - 1; i >= 0; i–) {
maxHeap(nums, 0, i);
swap(nums, 0, i);
}
}
public void maxHeap(int[] heap, int start, int end) {
if (start == end) {
return;
}
int parent = start;
int childLeft = start * 2 + 1;
int childRight = childLeft + 1;
if (childLeft <= end) {
maxHeap(heap, childLeft, end);
if (heap[childLeft] > heap[parent]) {
swap(heap, parent, childLeft);
}
}
if (childRight <= end) {
maxHeap(heap, childRight, end);
if (heap[childRight] > heap[parent]) {
swap(heap, parent, childRight);
}
}
}
private void swap(int[] nums, int a, int b) {
int t = nums[a];
nums[a] = nums[b];
nums[b] = t;
}
#####6.7归并排序
归并排序采用分治的思想:
- Divide:将n个元素平均划分为各含n/2个元素的子序列;
- Conquer:递归的解决俩个规模为n/2的子问题;
- Combine:合并俩个已排序的子序列。
性能:时间复杂度总是为O(NlogN),空间复杂度也总为为O(N),算法与初始序列无关,排序是稳定的。
public void mergeSort(int[] array, int start, int end, int[] temp) {
if (start >= end) {
return;
}
int mid = (start + end) / 2;
mergeSort(array, start, mid, temp);
mergeSort(array, mid + 1, end, temp);
int f = start, s = mid + 1;
int t = 0;
while (f <= mid && s <= end) {
if (array[f] < array[s]) {
temp[t++] = array[f++];
} else {
temp[t++] = array[s++];
}
}
while (f <= mid) {
temp[t++] = array[f++];
}
while (s <= end) {
temp[t++] = array[s++];
}
for (int i = 0, j = start; i < t; i++) {
array[j++] = temp[i];
}
}
#####6.8基数排序
对于有d个关键字时,可以分别按关键字进行排序。有俩种方法:
- MSD:先从高位开始进行排序,在每个关键字上,可采用基数排序
- LSD:先从低位开始进行排序,在每个关键字上,可采用桶排序
即通过每个数的每位数字的大小来比较
//找出最大数字的位数
int maxNum(int arr[], int len) {
int _max = 0;
for (int i = 0; i < len; ++i) {
int d = 0;
int a = arr[i];
while (a) {
a /= 10;
d++;
}
if (_max < d) {
_max = d;
}
}
return _max;
}
void radixSort(int *arr, int len) {
int d = maxNum(arr, len);
int *temp = new int[len];
int count[10];
int radix = 1;
for (int i = 0; i < d; ++i) {
for (int j = 0; j < 10; ++j) {
count[j] = 0;
}
for (int k = 0; k < len; ++k) {
count[(arr[k] / radix) % 10]++;
}
for (int l = 1; l < 10; ++l) {
count[l] += count[l - 1];
}
for (int m = 0; m < len; ++m) {
int index = (arr[m] / radix) % 10;
temp[count[index] - 1] = arr[m];
count[index]–;
}
for (int n = 0; n < len; ++n) {
arr[n] = temp[n];
}
radix *= 10;
}
delete (temp);
}
#####6.9拓扑排序
在有向图中找拓扑序列的过程,就是拓扑排序。拓扑序列常常用于判定图是否有环。
- 从有向图中选择一个入度为0的结点,输出它。
- 将这个结点以及该结点出发的所有边从图中删除。
- 重复前两步,直到没有入度为0的点。
如果所有点都被输出,即存在一个拓扑序列,则图没有环。
###七.跳跃表
跳跃列表是一种数据结构。它允许快速查询一个有序连续元素的数据链表。跳跃列表的平均查找和插入时间复杂度都是 O(log n)
,优于普通队列的 O(n)
。
快速查询是通过维护一个多层次的链表,且每一层链表中的元素是前一层链表元素的子集。一开始时,算法在最稀疏的层次进行搜索,直至需要查找的元素在该层两个相邻的元素中间。这时,算法将跳转到下一个层次,重复刚才的搜索,直到找到需要查找的元素为止。跳过的元素的方法可以是 随机性选择 或 确定性选择,其中前者更为常见。
在查找目标元素时,从顶层列表、头元素起步。算法沿着每层链表搜索,直至找到一个大于或等于目标的元素,或者到达当前层列表末尾。如果该元素等于目标元素,则表明该元素已被找到;如果该元素大于目标元素或已到达链表末尾,则退回到当前层的上一个元素,然后转入下一层进行搜索。
跳跃列表不像平衡树等数据结构那样提供对最坏情况的性能保证:由于用来建造跳跃列表采用随机选取元素进入更高层的方法,在小概率情况下会生成一个不平衡的跳跃列表(最坏情况例如最底层仅有一个元素进入了更高层,此时跳跃列表的查找与普通列表一致)。但是在实际中它通常工作良好,随机化平衡方案也比平衡二叉查找树等数据结构中使用的确定性平衡方案容易实现。跳跃列表在并行计算中也很有用:插入可以在跳跃列表不同的部分并行地进行,而不用对数据结构进行全局的重新平衡。
跳跃表插入一个元素:
#####实现
因为跳跃列表中的元素可以在多个列表中,所以每个元素可以有多于一个指针。跳跃列表的插入和删除的实现与普通的链表操作类似,但高层元素必须在进行多个链表中进行插入或删除。
package io.github.hadyang.leetcode.algo;
import lombok.G
etter;
import lombok.Setter;
import java.util.Arrays;
import java.util.Random;
/**
- @author haoyang.shi
*/
public class SkipList<K extends Comparable, V> {
@Getter
@Setter
static final class Node<K extends Comparable, V> {
private K key;
private V value;
private Node<K, V> up, down, pre, next;
Node(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public String toString() {
return “Node{” +
“key=” + key +
“, value=” + value +
“, hashcode=” + hashCode() +
“, up=” + (up == null ? “null” : up.hashCode()) +
“, down=” + (down == null ? “null” : down.hashCode()) +
“, pre=” + (pre == null ? “null” : pre.hashCode()) +
“, next=” + (next == null ? “null” : next.hashCode()) +
‘}’;
}
}
private Node<K, V> head;//k,v都是NULL
private Integer levels = 0;
private Integer length = 0;
private Random random = new Random(System.currentTimeMillis());
public SkipList() {
createNewLevel();
}
public void put(K key, V value) {
if (key == null || value == null) {
return;
}
Node<K, V> newNode = new Node<>(key, value);
insertNode(newNode);
}
private void insertNode(Node<K, V> newNode) {
Node<K, V> curNode = findNode(newNode.getKey());
if (curNode.getKey() == null) {
insertNext(curNode, newNode);
} else if (curNode.getKey().compareTo(newNode.getKey()) == 0) {
//update
curNode.setValue(newNode.getValue());
return;
} else {
insertNext(curNode, newNode);
}
int currentLevel = 1;
Node<K, V> oldTop = newNode;
while (random.nextInt(100) < 50) {
Node<K, V> newTop = new Node<>(newNode.getKey(), null);
if (currentLevel >= levels) {
createNewLevel();
}
while (curNode.getPre() != null && curNode.getUp() == null) {
curNode = curNode.getPre();
}
if (curNode.getUp() == null) {
continue;
}
curNode = curNode.getUp();
Node<K, V> curNodeNext = curNode.getNext();
curNode.setNext(newTop);
newTop.setPre(curNode);
newTop.setDown(oldTop);
oldTop.setUp(newTop);
newTop.setNext(curNodeNext);
oldTop = newTop;
currentLevel++;
}
}
private void createNewLevel() {
Node<K, V> newHead = new Node<>(null, null);
if (this.head == null) {
this.head = newHead;
this.levels++;
return;
}
this.head.setUp(newHead);
newHead.setDown(this.head);
this.head = newHead;
this.levels++;
}
private void insertNext(Node<K, V> curNode, Node<K, V> newNode) {
Node<K, V> curNodeNext = curNode.getNext();
newNode.setNext(curNodeNext);
if (curNodeNext != null) {
curNodeNext.setPre(newNode);
}
curNode.setNext(newNode);
newNode.setPre(curNode);
this.length++;
}
public V get(K key) {
Node<K, V> node = findNode(key);
if (key.equals(node.getKey())) {
return node.getValue();
}
return null;
}
private Node<K, V> findNode(K key) {
Node<K, V> curNode = this.head;
for (; ; ) {
wNode) {
Node<K, V> curNodeNext = curNode.getNext();
newNode.setNext(curNodeNext);
if (curNodeNext != null) {
curNodeNext.setPre(newNode);
}
curNode.setNext(newNode);
newNode.setPre(curNode);
this.length++;
}
public V get(K key) {
Node<K, V> node = findNode(key);
if (key.equals(node.getKey())) {
return node.getValue();
}
return null;
}
private Node<K, V> findNode(K key) {
Node<K, V> curNode = this.head;
for (; ; ) {