1.我的目的
很久没有写过基本的排序算法了,想实现一下;最近应该会把基本排序算法都回忆一遍。写一个记录一个吧(以下代码都在leetcode上跑通过所有数据且时间复杂度在预期范围内)
2.代码
归并排序—我的代码:
let s = [1,2,5,3,5,1,4];
let funcA = function (arr) {
let l = parseInt(arr.length/2);
if (l > 0) {
let left = funcA(arr.slice(0,l));
let right = funcA(arr.slice(l));
let leftIndex = left.length;
let rightIndex = right.length;
let resp = [];
let leftTemp = 0;
let rightTemp = 0;
while (leftTemp < leftIndex && rightTemp < rightIndex) {
if (left[leftTemp] < right[rightTemp]) {
resp.push(left[leftTemp]);
leftTemp++;
}
else {
resp.push(right[rightTemp]);
rightTemp++;
}
};
if (leftTemp === leftIndex) {
resp = [...resp, ...right.slice(rightTemp)];
}
if (rightTemp === rightIndex) {
resp = [...resp, ...left.slice(leftTemp)];
}
return resp;
} else {
return arr;
}
}
console.log(funcA(s))
时间复杂度包括 拆解数组 + 合并排序数组
拆解数组时间为为
l
o
g
2
n
log_2{n}
log2n
合并排序的时候为 合并*排序 合并为
l
o
g
2
n
log_2{n}
log2n ,我们每比较一个值就会插入到返回数组中,所以最多比较n次;即时间复杂度为n
l
o
g
2
n
log_2{n}
log2n
快速排序—我的代码:
let sortList = [2,34,3,6,1,21,53,10,21,6]
let quickSort = function (left, right, sortList) {
let sign = sortList[right]
let leftSign = left
let rightSign = right
while (leftSign < rightSign) {
while (sortList[leftSign] <= sign && leftSign < rightSign) {
leftSign++
}
sortList[rightSign] = sortList[leftSign]
while (sortList[rightSign] >= sign && leftSign < rightSign) {
rightSign--
}
sortList[leftSign] = sortList[rightSign]
}
sortList[leftSign] = sign
if (left < leftSign-1) {
quickSort(left, leftSign-1, sortList)
}
if (right > rightSign+1) {
quickSort(rightSign+1, right, sortList)
}
}
quickSort(0, sortList.length-1, sortList)
console.log(sortList)
时间复杂度包括 排列次数(层数)*每一层排列时间
容易想到 排列层数是变化的,所以快排也被称为不稳定的;
在最优情况下是一个等比数列 也就是2(1-
2
k
2^k
2k)/(1-2) = n 得到 k =
l
o
g
2
n
log_2{n}
log2n , 然后每层都需要排列n个数,所以是O(n
l
o
g
2
n
log_2{n}
log2n)
在最坏情况,也就是不能刚好二分的去排列了,层数就会变高,最坏情况就是每层都在最左边或者最右边,那么也就是要排列n层,事件复杂度就是O(
n
2
n^2
n2)
注意事项:①while的时候带上leftSign < rightSign,因为可能直接一路比较都是符合条件使得leftSign大于rightSign了
归并排序----我的代码
let sortList = [-4, 0, 7, 4, 9, -5, -1, 0, -7, -1]
let lastLeaf = parseInt((sortList.length - 1) / 2)
let sortOnetime = function (arr, i, lenSign) {
let sign = i
if (arr[2 * i + 1] !== undefined && arr[2 * i + 1] > arr[sign] && (2 * i + 1) <= lenSign) {
sign = 2 * i + 1
}
if (arr[2 * i + 2] !== undefined && arr[2 * i + 2] > arr[sign] && (2 * i + 2) <= lenSign) {
sign = 2 * i + 2
}
if (sign !== i){
let temp = arr[sign]
arr[sign] = arr[i]
arr[i] = temp
sortOnetime(arr, sign, lenSign)
}
}
// 初始化一个升序堆
for (let i = lastLeaf; i >= 0; i--) {
sortOnetime(sortList, i, sortList.length - 1)
}
// 交换首尾并重新排序堆
for (let i = sortList.length - 1; i > 0; i--) {
sortOnetime(sortList, 0, i)
let temp = sortList[i]
sortList[i] = sortList[0]
sortList[0] = temp
}
console.log(sortList)
注意:
一开始自己犯了两个错误:
1.每次在排序堆的时候左边右边比较如果都大于父节点,我会对两个子节点自身继续进行排序, 而这样是不必要的,大大增加了时间复杂度 ,哭```; 因为父节点的子节点肯定是各自有序的,是调整之后可能对其有序造成影响,所以只需要取递归排序调换的那一边(也就是最大的那一边)就可以了
2.在对换堆顶和堆尾元素之后,要记得把重排序的长度减1,被影响的节点排序时也应遵守这个长度限制
堆排序的讲解网上一大堆,就不赘述了,重点理解为什么要对改变顺序的子节点进行继续排序。 举一个比较实际的例子: 堆[11,9,7,7,8,6] 我们交换首尾=>[6,9,7,7,8,11]然后比较当前节点并替换 [9,6,7,7,8,11] ; 到这里,仔细观察堆 明显678的父子节点不符合堆定义,所以为了解决这种问题,我们还是需要对替换过的节点继续进行排序操作的。
时间复杂度:
初始化堆 + 排列每个节点
说下排列每个节点:每一次排列我们就想着是堆头排到叶子节点去,这样需要操作的次数是最多了,在这种情况下,叶子节点高度Log(n+1) ,从堆首操作下去,每一高度操作一次,也就是操作log(n+1)次了,所以随着需要排序的堆数量变少,时间复杂度为log(n+1) + log(n) + log(n-1) + ````log(2) 这个等式和nlog(n)同阶(这个网上也有很多大佬做了数学方面的证明,这里不记录了),所以我们一般也归为O(nlog(n))