一、内部排序
汇总
说明
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面; 不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
内排序:所有排序操作都在内存中完成; 外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
1. 交换位置排序
冒泡排序
- 冒泡排序:时间复杂度为O(n^2)
- 思路:第一轮,两两比较,把最小的放到最后面,第二轮,对除了最后面的一个剩下的再冒泡排序,相当于一次处理一个
- 优化1:设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可
- 优化2:传统冒泡排序中每一趟排序操作只能找到一个最大值或最小值,我们考虑利用在每趟排序中进行正向和反向两遍冒泡的方法一次可以得到两个最终值(最大者和最小者) , 从而使排序趟数几乎减少了一半。
function _sort(array) {
// 补全代码
let temp
let k = array.length
for (let i = 0; i < array.length; i++) {
let flag = 1
for (let j = 0; j < k; j++) {
if (array[j] < array[j + 1]) {
[array[j], array[j + 1]] = [array[j + 1], array[j]]
flag = 0
k = j
}
console.log(array);
}
if (flag) {
return
}
}
return array
}
快速排序
Array.prototype.quickSort = function () {
const rec = (arr) => {
if (arr.length <= 1) {
return arr
}
const left = []
const right = []
const mid = arr[0]
/* 这里基准主元我直接用了头部元素 */
for (let i = 1; i < arr.length; i++) {
if (arr[i] < mid) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return [...rec(left), mid, ...rec(right)]
}
const res = rec(this) //调用递归
res.forEach((n, i) => {
this[i] = n //将排序好的res数组赋值给this
});
return this
}
const arr = [2, 4, 5, 3, 1]
console.log(arr.quickSort())
2. 选择排序
简单选择排序:O(n^2)
- 思路:选择最大的,放到前面
function _sort(array) {
for (let i = 0; i < array.length - 1; i++) {
let max = i
for (let j = i + 1; j < array.length; j++) {
if (arr[j] > arr[max]) {
max = j
}
}
[arr[i], arr[max]] = [arr[max], arr[i]]
console.log(array);
}
}
堆排序
-
每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。
使用堆排序只需要两步: 1.把数组转化为大顶堆 2.把大顶堆元素放在序列最后,剩余数字继续调整为大顶堆
- 第一步:如何把数组调整为大顶堆? 先使用完全二叉树的顺序存储,把数组转化为一个堆(二叉树) 从最后一个叶子节点(最后一层)向上处理,如果该子节点比他的父节点小,二者互换位置,但要注意移动后会打乱原来的排序, 也就是说需要再次比较新的节点是否比他之下的子节点小,如果小的话还需移动
- 把堆顶最大的元素固定到末尾,然后使用递归把剩余元素再次排成大顶堆
3. 插入排序
直接插入排序
- 最好情况的时间复杂度是O(n),最坏情况的时间复杂度是O(n^2)
- 思路:从右边取未排序的,和左边已经排序的比较,判断该数组的插入位置,把该插入位置之后的数字往后面挪移
function _sort(array) {
var len = arr.length;
for (var i = 1; i < len; i++) {
var temp = arr[i];
var j = i - 1;//默认已排序的元素
// while (j >= 0 && arr[j] > temp) { //在已排序好的队列中从后向前扫描
// arr[j + 1] = arr[j]; //已排序的元素大于新元素,将该元素移到一下个位置
// j--;
// }
for (j; j >= 0; j--) {
if (arr[j] < temp) {
// 由大到小
arr[j + 1] = arr[j]
}
else {
break
}
}
arr[j + 1] = temp;//j+1就是需要插入的位置
}
return console.log(arr);
}
希尔排序
- 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
Array.prototype.shellSort = function () {
for (let D = Math.floor(this.length / 2); D > 0; D = Math.floor(D / 2)) { /* 选取增量序列 */
for (let i = D; i < this.length; i += D) { /* 插入排序(将原来的1改为了D) */
let temp = this[i]
let j = i
while (j >= D) {
if (this[j - D] > temp) {
this[j] = this[j - D]
} else {
break
}
j -= D
}
this[j] = temp
}
}
return this
}
console.log([2, 4, 5, 3, 1, 2, 5, 6, 7, 9, 1, 5, -1, 2, 6, 4, 72, 6, 8, 4].shellSort())
后面的排序较为复杂,暂时只需要了解
归并排序
- 稳定排序,时间复杂度nlogn logn是分阶段的,n是治阶段的
基数排序
在新的数组中,进行第二轮,按照十位数排序,依次存放于桶中,按照之前的顺序取出,组成新的数组。 进行第三轮,按照百位数排序:
将百位数的元素取出之后,我们发现新的数组已经变成了有序数组
外部排序
计数排序
- 花O(n)的时间扫描一下整个序列 A,获取最小值 min 和最大值 max
- 开辟一块新的空间创建新的数组 B,长度为 ( max - min + 1)
- 数组 B 中 index 的元素记录的值是 A 中某元素出现的次数
- 最后输出目标整数序列,具体的逻辑是遍历数组 B,输出相应元素以及对应的个数