目录
快速排序
1.从序列中选择一个轴点元素pivot 假设每次选择索引为0的元素为轴点元素
2.利用pivot将序列分割成2个子序列:将小于pivot的元素放在pivot的前面(左边)
将大于pivot的元素放在pivot的后面(右边) 等于pivot的元素放在哪边都可以
3.对子序列进行上述1 2步骤 直到不能再分割(子序列中只剩下1个元素)
快速排序的本质:逐渐将每一个元素都转换成轴点元素
实现
- 假设数组为array={7,1,2,3,4,5,6} 最初pivot=7 进行一轮快速排序后array={6,1,2,3,4,5,7} 然后pivot=6 进行一轮快速排序后array={5,1,2,3,4,6,7}……显然 每次轴点元素都是数组中最大的元素 会对数组进行一次彻底的完全的遍历排序(也就是说不会出现左边元素小于轴点元素 不用操作 直接begin++ 或者右边元素大于轴点元素 不用操作 直接end-- 这两种情况)会降低排序性能 所以不能永远选索引为0的元素为轴点元素 为了降低最坏情况出现的概率 我们每次在数组[begin,end)范围中随机选一个数 把随机数和begin位置的元素进行交换 每次还是获取索引为0的元素为轴点元素
- 执行过程:先备份轴点元素 再用array[end]元素和轴点元素进行比较 若end元素大 则end-- 然后用array[end]元素和轴点元素进行比较 若轴点元素>=end元素 则把end元素放在begin位置 begin++ 此时调换比较方向 然后用array[begin]元素和轴点元素进行比较 若轴点元素大 则begin++ 然后用array[begin]元素和轴点元素进行比较 若轴点元素<=begin元素 则把begin元素放在end位置 end-- 此时调换比较方向……直到begin=end 则数据比较完毕 将轴点元素插入begin或end位置
- 为啥要备份:第一次用array[end]元素和轴点元素进行比较时 若end小 则把end元素放在begin位置 begin++ 会覆盖轴点元素
- 为啥最初从end元素开始比较:因为begin元素就是轴点元素 难道要轴点元素和轴点元素进行比较?
- 注意元素等于轴点元素时 要对元素进行处理 把元素放在自己的对立面 也就是说 end元素=轴点元素 end放在begin的位置 begin元素=轴点元素 begin放在end的位置 为什么要这么做:假设数组array={6,6,6,6,6,6} 随机轴点元素肯定是6 这个6和索引为0的6交换位置 array={6a,6b,6c,6d,6e,6f} 注:abcdef表示不同的6 第一个6是轴点元素 然后进行排序 假设相等元素不操作 直接让begin++或end-- 那么排序后的数组依旧是array={6a,6b,6c,6d,6e,6f} 分割出来的子序列极度不均匀 整个数组没有进行任何操作 6a依旧是轴点元素 排序和不排序没有任何区别 浪费资源 根据快速排序的规定: 对子序列进行上述1 2步骤 直到不能再分割(子序列中只剩下1个元素) 显然刚才的排序并没有达到快速排序的规定 还会继续排序 继续排序也是原来的数组 没有意义 程序会一直执行 所以对于相等的元素 要将其放在自己的对立面 才能将序列分割成2个均匀的子序列
代码如下:
JAVA:
public class QuickSort<E extends Comparable<E>> extends sort.cmp.Sort<E> {
@Override
protected void sort() {
sort(0,array.length);
}
private void sort(int begin, int end) {//对[begin,end)范围内的元素进行快速排序
if (end - begin < 2) return;
//确定轴点位置
int mid = pivotIndex(begin,end);
//对子序列进行快速排序
sort(begin,mid);
sort(mid + 1,end);
}
private int pivotIndex(int begin, int end){
//构造出[begin,end)范围的轴点元素 返回值是轴点元素的最终位置
//随机选择一个元素和begin位置的元素交换
swap(begin, begin + (int) (Math.random() * (end - begin)));
//备份begin位置的元素
E pivot = array[begin];
//end指向最后一个元素
end--;
while (begin < end){
while (begin < end){
if (cmp(pivot,array[end]) < 0){//右边元素>轴点元素
end--;
}else {//右边元素<=轴点元素
array[begin++] = array[end];
break;
}
}
while (begin < end){
if (cmp(pivot,array[begin]) > 0){//左边元素<轴点元素
begin++;
}else {//左边元素>=轴点元素
array[end--] = array[begin];
break;
}
}
}
//将轴点元素放入最终位置
array[begin] = pivot;
//返回轴点元素的位置
return begin;
}
}
归并排序
不断将当前序列平均分割成2个子序列 直到不能再分割(序列中只剩1个元素)
不断将两个子序列合并成一个有序序列 直到最终只剩下一个有序序列
实现
- 令li是左边数组的第一个索引 le是左边数组的最后一个索引 ri是左边数组的第一个索引 re是左边数组的最后一个索引 ai是合并数组的最后一个元素的索引+1
- 我们对array数组进行归并排序 先将array数组的元素分为一个一个 然后再对这一个个局部数组进行合并 显然 我们合并后的数组依旧是array数组 注意:当我们把右边数组的元素放在array[0]的位置时 会破坏左边数组 所以我们必须先备份左边数组leftArray 然后取出左边数组的第一个元素和右边数组的第一个元素进行比较 若右边小于左边 则将右边的元素放在array数组中索引为ai的位置 然后ri++ ai++ 然后取出左边数组的第一个元素和右边数组的第二个元素进行比较 若右边大于左边 则将左边的元素放在array数组中索引为ai的位置 然后li++ ai++ 如此循环执行 直到两数组中某一方为空
- 若左边为空 则结束 因为两个数组肯定分别都是排好序的 我是把两个排好序的数组变为一个排好序的数组 若右边为空 则把左边剩余的元素依次移动到array中
*/
public class MergeSort<E extends Comparable<E>> extends sort.cmp.Sort<E> {
private E[] leftArray;
@Override
protected void sort() {
leftArray = (E[])new Comparable[array.length >> 1];
sort(0,array.length);
}
/*
用递归分割 把一个数组分为两个数组 再把这两个数组分别分割为两个数组 得到四个数组……
显然 分割的本质就是把一个数组一分为二 所以可以用递归
*/
private void sort(int begin, int end) {//对[begin,end)范围的数据进行归并排序
//end - begin为数组长度 数组长度小于2说明数组中只有一个或0个元素
if (end - begin < 2) return;
int mid = (begin + end) >> 1;
sort(begin,mid);
sort(mid,end);
merge(begin,mid,end);
}
private void merge(int begin,int mid,int end) {//将[begin,mid)和[mid,end)范围的序列合并成一个有序序列
int li = 0,le = mid - begin;
int ri = mid,re = end;
int ai = begin;
for (int i = li; i < le; i++) {//备份左边数组
leftArray[i] = array[begin + i];
}
while (li < le){//如果左边还没结束
if (ri < re && cmp(array[ri],leftArray[li]) < 0){
array[ai++] = array[ri++];
}else {//右边为空 则把左边剩余的元素依次移动到array中
array[ai++] = leftArray[li++];
}
}
}
}
插入排序
类似于打扑克时所用的排序
实现
- 在执行过程中 插入排序将数组分为两部分:头部是已经排好序的 尾部是待排序的
- 从头开始扫描每一个元素 每当扫描到一个元素 就将他插入到头部合适的位置(利用二分查找实现) 使得头部数据依旧保持有序 执行过程和打扑克摸牌差不多 最开始你手上有一张牌index[0] 这张牌可看做头部 剩下的牌看做尾部 你摸了一张牌index[1] 和手里的牌比较 然后放在合适的位置 现在你手里的两张牌相当于头部 你又摸牌index[2] 和index[1]比较 若index[1]大 那么index[2]和index[1]交换位置 然后index[2]和index[0]比较 若index[2]大 则不交换继续摸牌
function InsertionSort() {
for (let begin = 1; begin < array.length; begin++) {
insert(begin,search(begin));
}
}
function insert(source, dest) {//将source位置的元素插入到dest位置
let v = array[source];
for (let i = source; i > dest; i--) {
array[i] = array[i - 1];
}
array[dest] = v;
}
function search(index){
let begin = 0;
let end = index;
while (begin < end){
let mid = (begin + end) >> 1;
if (array[index] < array[mid]){
end = mid;
}else {
begin = mid + 1;
}
}
return begin;//begin = end
}
let array = [9,8,7,6,5,4,3,2,1];
InsertionSort();
console.log(array);
215. 数组中的第K个最大元素
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
解:方法一(快排):选定轴点元素 进行快排一次 就可以得到轴点元素的位置left 在升序数组中 第K大的元素位于n-k索引的位置 所以比较left与n-k 若left<n-k 第K大的元素一定在轴点元素的右边 去右边找 对右边数组进行快排(选定轴点元素 比较……)若left>n-k 第K大的元素一定在轴点元素的左边 去左边找 对左边数组进行快排(选定轴点元素 比较……)若left=n-k 找到第K大元素 返回
代码如下:
var findKthLargest = function(nums, k) {
let start = 0;
let n = nums.length;
let end = n - 1;
function quick (left, right) {
let num = nums[left];
while (left < right) {
while (left < right) {
if (num < nums[right]) {
right--;
}else {
nums[left++] = nums[right];
break;
}
}
while (left < right) {
if (num > nums[left]) {
left++;
}else {
nums[right--] = nums[left];
break;
}
}
}
nums[left] = num;
return left;
}
let index = quick(start, end);
while (index !== n - k) {
if (index < n - k) {
start = index + 1;
index = quick(start, end);
}else {
end = index - 1;
index = quick(start, end);
}
}
return nums[index];
}
剑指 Offer 45. 把数组排成最小的数
输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
示例 1:
输入: [10,2]
输出: “102”
示例 2:
输入: [3,30,34,5,9]
输出: “3033459”
解:设数组nums中任意两数字的字符串为x和y,则规定排序判断规则 为:
若拼接字符串 x + y > y + x,则x “大于” y ; 反之,若 x + y < y + x,则 x “小于” y ;
x “小于” y 代表:排序完成后,数组中 x 应在 y 左边;“大于” 则反之。
根据以上规则,套用任何排序方法对 nums 执行排序即可。
代码如下:
JS内置函数:
var minNumber = function(nums) {
return nums.sort((a, b) => ('' + a + b) - ('' + b + a)).join('');
}
JS快排:
function minNumber(nums) {
quickSort(nums, 0, nums.length - 1)
console.log()
return nums.join('')
}
// ! 该题其实考查的就是排序 但是对比大小的条件变了 数字的话根据大小来比
// ! 把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个 满足这种对比是根据 a+b 和 b+a的比较 决定排序位置
function quickSort(nums, l, r) {
if (l >= r) return
let i = l,
j = r
tmp = nums[i]
const pivot = l
while (i < j) {
while ('' + nums[j] + nums[pivot] - ('' + nums[pivot] + nums[j]) >= 0 && i < j) j--
while ('' + nums[i] + nums[pivot] - ('' + nums[pivot] + nums[i]) <= 0 && i < j) i++
tmp = nums[i]
nums[i] = nums[j]
nums[j] = tmp
}
nums[i] = nums[pivot]
nums[pivot] = tmp
quickSort(nums, l, i - 1)
quickSort(nums, i + 1, r)
}
剑指 Offer 51. 数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
解:「归并排序」与「逆序对」是息息相关的。归并排序体现了 “分而治之” 的算法思想,具体为:
分: 不断将数组从中点位置划分开(即二分法),将整个数组的排序问题转化为子数组的排序问题;
治: 划分到子数组长度为 1 时,开始向上合并,不断将 较短排序数组 合并为 较长排序数组,直至合并至原数组时完成排序;
合并阶段 本质上是 合并两个排序数组 的过程,而每当遇到 左子数组当前元素 > 右子数组当前元素 时,意味着 「左子数组当前元素 至 末尾元素」 与 「右子数组当前元素」 构成了若干 「逆序对」 。
本题利用归并排序 改写归并排序 在并的时候统计逆序对 下图演示其中某一部分过程:
1710. 卡车上的最大单元数
请你将一些箱子装在 一辆卡车 上。给你一个二维数组 boxTypes ,其中 boxTypes[i] = [numberOfBoxesi, numberOfUnitsPerBoxi] :
numberOfBoxesi 是类型 i 的箱子的数量。
numberOfUnitsPerBoxi 是类型 i 每个箱子可以装载的单元数量。
整数 truckSize 表示卡车上可以装载 箱子 的 最大数量 。只要箱子数量不超过 truckSize ,你就可以选择任意箱子装到卡车上。
返回卡车可以装载 单元 的 最大 总数。
示例 1:
输入:boxTypes = [[1,3],[2,2],[3,1]], truckSize = 4
输出:8
解释:箱子的情况如下:
1 个第一类的箱子,里面含 3 个单元。
2 个第二类的箱子,每个里面含 2 个单元。
3 个第三类的箱子,每个里面含 1 个单元。
可以选择第一类和第二类的所有箱子,以及第三类的一个箱子。
单元总数 = (1 * 3) + (2 * 2) + (1 * 1) = 8
示例 2:
输入:boxTypes = [[5,10],[2,5],[4,7],[3,9]], truckSize = 10
输出:91
解:将二维数组按照第二维降序排序:Arrays.sort(boxTypes,(a,b)->b[1]-a[1]);
,然后遍历二维数组,如果第一维小于truckSize,则装载该类型的盒子,且truckSize减少数量,如果第一维大于truckSize,则取truckSize,如果装满了(truckSize = 0
)就直接返回,在for循环中判断是否装满了
public static int maximumUnits(int[][] boxTypes, int truckSize) {
int ans = 0;
// 二维数组按照第二维降序排序 [[5,10],[2,5],[4,7],[3,9]]
Arrays.sort(boxTypes,(a,b)->b[1]-a[1]);
for (int i = 0; i < boxTypes.length && truckSize != 0; i++) {
if (boxTypes[i][0] <= truckSize) {
truckSize -= boxTypes[i][0];
ans += boxTypes[i][0] * boxTypes[i][1];
}else {// boxTypes[i][0] > truckSize
ans += truckSize * boxTypes[i][1];
truckSize = 0;
}
}
return ans;
}