第一章
冒泡排序
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) return;
for (int e = arr.length - 1; e > 0; e--) {
for (int i = 0; i < e; i++) {//最大的浮动到当前的右边界
if (arr[i] > arr[i + 1])
swap(arr, i, i + 1);
}
}
}
选择排序
public static void selectionSort(int[] arr) {
if (arr == null || arr.length < 2) return;
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
minIndex = arr[j] < arr[minIndex] ? j : minIndex;
}
swap(arr, i, minIndex);
}
}
插入排序
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) return;
for (int i = 1; i < arr.length; i++) { // i-1前的已经有序
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {// arr[i]不断和前面的比较,
swap(arr, j, j + 1);
}
}
}
public static void swap(int[] arr, int i, int j) {
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
Master定理
子问题规模等量, 才能使用
二分 使用master定理
a:调用次数;2
N/b:调用规模;N/2
除了子问题之外剩下的时间复杂度:O(1)
因此, T(N) = 2T(N/2)+O(1);
第二章 O(NlogN)的排序算法
归并排序 问题分析
小和问题(和归并排序一样,但是同时求小和)
分析: 求一个数左边比他小的数之和,其实就是求一个数右边比他大的数的个数,再乘以原数字。
res 是小和,(1)当右边p2值比p1的大时,说明p2之后的值也比p1的大,因此一共有r-p2+1个数比p1大。 (2)如果p1的值较大则不需要加,即+0。
辅助数组是<而不是<=,保证了相等情况下右边先入。
左小和 + 右小和 +归并时的小和
求一个数右边有 多少个数 比当前数大,把 个数n 乘以这个数。
merge时,左右组有元素相等,右组先拷贝且不产生小和,则保证了快速得到个数。
逆序对问题
求一个数右边有 多少个数 比他小,和上面一样。其实就是求一个数,左边有多少个数比他大。
这两个问题的关键都是,转化问题说法,因为归并的性质是小的先入,所以让找 哪边有多少数比他大较好,可以直接用 右边界-当前坐标+1
得出比他大的个数(都是递增的)。
num += arr[p2] < arr[p1] ? m-p1+1 : 0;
help[i++] = arr[p1] < arr[p2] arr[p1] : arrp2]; //相等左边先入
快排
快排1.0
普通快排
快排2.0
适合key有很多重复值
[2,3,4,4,4,5]
p[0]=2, p[1]=4
相比1.0, 可以不用管中间的了
public static void quickSort(int[] arr, int l, int r) {
if (l < r) {
swap(arr, l + (int) (Math.random() * (r - l + 1)), r);//随机选择key,然后和最后一个交换,然后继续
int[] p = partition(arr, l, r);
quickSort(arr, l, p[0] - 1); <区
quickSort(arr, p[1] + 1, r); >区
}
}
public static int[] partition(int[] arr, int l, int r) {
int less = l - 1;//小于区
int more = r;//大于区
while (l < more) {
if (arr[l] < arr[r]) {
swap(arr, ++less, l++);
} else if (arr[l] > arr[r]) {
swap(arr, --more, l);
} else {
l++;
}
}
swap(arr, more, r);
return new int[] { less + 1, more };
}
选最后一个为key
,数字和key一样时候,右边第一个和 末尾的key换。
快排3.0
随机选择key,不然如果是1,2,3,4,5,6,7,8,9这样的数组时间复杂度就是n^2了。
堆排序
完全二叉树,用数组表示
左子树 2\*i+1
;
右子树 2\*i+2
;
父节点 (i-1)/2
;
大顶堆
-
heapInsert
a. 新来的不断和父 pk,大了就上去。O(logN)树高logN
b. 到了顶部时,自己不比自己大就结束,一个while搞定
注 : 可用于往堆里添加元素:
(1)把key加到末尾。
(2)index=heapSize-1;
(3)往上heapInsert; -
弹出堆顶元素
heapify
a. 把最后一个换到堆顶,heapsize–;
b. 然后不断和自己俩孩子的最大值pk,小了就下去(交换)。O(logN)树高logN
注 : 可用于弹出堆顶元素:
(1)把最后一个换到堆顶,heapSize–;
(2)index=0,往下heapify。
题目1:给定一个堆,把i位置的数改一下。
分析:
1. 知道变大变小
变小了,往下heapify
变大了,往上heapInsert
2. 不知道大小
先heapInsert,①能,就完事;
②不能就会停, 然后再heapify。
堆排序
得到大顶堆后:
- 把堆顶和末尾换,heapsize–;
- heapify,得到子 大顶堆
- 循环到heapsize==0;
注: 就是先构建顶堆,然后不断弹出的过程。每一个最大值到了末尾,最后数组就是升序的
如果给一堆数构建顶堆时
1.可以不从第一个heapInsert,O(N logN)
2.也可以从最后一个进行heapify。O(N)
额外空间复杂度
堆排序:O(1)
归并排序:O(N)
快速排序:O(logN)
快速排序, 即记录中点开辟的内存。 左边用完释放右边用,因此整个是 树高O(logN)级别的。 但是如果每次选的值都在最右侧,[1,2,3,4,5]那么额外空间复杂度就是O(N)
题目二
堆排序扩展题目
已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。
分析:
因为是几乎有序,所以0~6必然存在最小值。
(因为最小值应该在0处,最大从6处挪到0处,一共用6步)
0-6放到小顶堆,因为如果0在6位之后,0要移动的位数大于k=6了,0必然在0-6位。
那么每次弹出
堆顶放到最前面,再加入新元素就会有序。(弹出之后是仍是小根堆,heapsize=5,。然后让7入堆,heapsize=6。循环)
第三章 桶排序,排序总结
比较器
即,C++比较符重载
Arrays. sort(students, new IdAscendingComparator());//小根堆
大根堆同理。
桶排序
桶排序思想下的排序
1)计数排序
2)基数排序
桶排序思想下的排序都是不基于比较
的排序
2) 时间复杂度为O(N),额外空间负载度O(M)
3)应用范围有限,需要样本的数据状况满足桶的划分
计数排序
是一种O(n)的排序算法,其思路是开一个长度为 maxValue-minValue+1 的数组,然后
- 分配。扫描一遍原始数组,以当前值- minValue 作为下标,将该下标的计数器增1。
- 收集。扫描一遍计数器数组,按顺序把值收集起来。
举个例子, nums=[2, 1, 3, 1, 5] , 首先扫描一遍获取最小值和最大值, maxValue=5 , minValue=1 ,于是开一个长度为5的计数器数组 counter.
- 分配。统计每个元素出现的频率,得到 counter=[2, 1, 1, 0, 1] ,例如 counter[0] 表示值 0+minValue=1 出现了2次。
- 收集。 counter[0]=2 表示 1 出现了两次,那就向原始数组写入两个1, counter[1]=1 表示 2 出现了1次,那就向原始数组写入一个2,依次类推,最终原始数组变为 [1,1,2,3,5] ,排序好了。
计数排序本质上是一种特殊的桶排序,当桶的个数最大的时候,就是计数排序。
基数排序
-
准备10个桶,从个位开始,(先进先出)
然后倒出来,如下图数组 -
再次, 十位进行操作
-
再次, 百位进行操作
-
再次倒出得到[13, 17, 25, 72, 100]
注: 可以用栈实现
Code: (不用栈实现)
count[] 为当先位数<=i的个数。 在个位时, <=1只有21,11 (2个); <=2 有21,11,52,65 (4个); <=3多一个13(5个)。
help[] 调整后的数组,从右往左开始。
62, 2的词频为4,help[4-1] = 62, count[4]–;
同理, 52, 2的词频变为了3,help[3-1] = 52, count[3]–;
同理, 11, 1的词频变为了2,help[2-1] = 11, count[2]–;
最终个位完成就是[21, 11, 52, 62, 13 ]
循环。
稳定性
Define: 排序后 值相同的数
相对次序不变
注:
- 选择排序[3,3,3,1,3,3,3], 1和第一个3交换。不稳定。
- 冒泡排序[2,2,1,2,2], 值相等不交换就可以稳定。
- 插入排序[3,2,2], 值相等不交换就可以稳定。
- 归并排序[1,1,2,3]----[1,2,3], 值相等,左边先入就可以稳定。小和问题稳定性丧失。
- 快速排序[6,7,6,6,3], key=5, 3和第一个6换,不稳定。
常数项最低,一般先选
- 堆排序, 不稳定。
//space
//space
//space