- n: 数据规模
- k:“桶”的个数
- In-place: 占用常数内存,不占用额外内存
- Out-place: 占用额外内存
- 稳定性:排序后2个相等键值的顺序和排序之前它们的顺序相同
O(1), O(n), O(logn), O(nlogn) 的区别
1.冒泡排序
冒泡排序动画演示原理:
对一个数组里的前一个数和后一个数进行比较,把两者之间最大的数放在后面,循环一轮之后最大的就在末尾了。然后同理在排序剩下的n-1 ,n-2 …个数
为什么会有两个for循环
:之所以在最外层嵌套一个for循环是因为如果仅仅只有一个for循环,那么两两比较之后,只会让最大的值在数组末尾(也就是只执行了一轮),前面的数字依旧不是有序的。第一个for循环用于控制他的轮数,第二个控制比较的次数。
第二个for循环里面为什么要-i
:因为i表示的是已经排序好了的最大数的长度(从右数第i个数都是已经排序好了的),不勇再参与排序,为了更简洁快速,所以进行-i操作。
第一个循环为什么要-1:
不需要同自己比较,所需轮数只要数组长度-1就可以了。
第二个循环为什么要 - 1 - i:
因为比较的次数首先要排除同自身比较,所以-1,其次循环了i轮,那么就有i个元素已经排序好处于数组末尾,也应省略。
function bubbleSort(arr) {
for (let i = 0; i < arr.length - 1; i++) {
for (let j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
//数组元素位置替换
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
}
}
}
return arr;
}
2.选择排序
选择排序动画演示原理:
- 外层遍历数组,拿到下标保存
- 内层再次遍历数组,将每个元素与外层保存的下标对应元素进行比较,将较小的值的下标进行保存
- 内层遍历结束找到了最小值的下标,与外层保存的下标对应的值交换位置
为什么有 minIndex = i
:当判断arr[j] < arr[minIndex]如果不成立时,第二个的循环完成之后执行元素替换工作时,minIndex就会因不存在而报错。
为什么要len - 1
:因为在第二个循环里最后一项是会跟所有项进行比较的,所以第一个循环的数组的最后一个元素已经比较过,不用重复比较。
第二个循环为什么要i + 1
:因为经过元素替换后,i及i之前的下标的数组是已经排序好的最小元素,不用在参与比较。
function selectionSort(arr) {
var len = arr.length;
//最小值的下标
var minIndex;
for (var i = 0; i < len - 1; i++) {
minIndex = i;
for (var j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) {
//寻找最小的数
minIndex = j; //将最小数的索引保存
}
}
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]
}
return arr;
}
3.插入排序
插入排序动画演示原理:
遍历数组,从后向前进行比较,符合条件交换位置。
第一个循环为什么不从0开始:
因为是从后向前进行比较,下标为0的元素前面没有值,所以从0开始无意义。
第二个循环为什么进行--操作:
因为是从后向前进行比较
function insertSort(arr) {
for (let i = 1; i < arr.length; i++) {
for (let j = i; j > 0; j--) {
if (arr[j] < arr[j - 1]) {
[arr[j - 1], arr[j]] = [arr[j], arr[j - 1]]
}
}
}
return arr;
}
4.快速排序
原理
- 先从数列中取出一个数作为“基准”。
- 分区过程:将比这个“基准”大的数全放到“基准”的右边,小于或等于“基准”的数全放到“基准”的左边。
- 再对左右区间重复第二步,直到各区间只有一个数。
quickSort(left).concat([middleItem], quickSort(right)):
这里是利用递归调用函数进行不断的分段排序,最后使用concat方法合并。注意这里的合并顺序必须是先左分段数组,在基准值,最后在右边的数组。
arr.splice(midINdex, 1)[0]:
使用splice取到中间基值,此方法修改掉了原数组。因为每次最后的拼接都会将中间基值拼接起来,如果不删除基值会造成拼接重复。
function quickSort(arr) {
if (arr.length <= 1) {
return arr;
}
let len = arr.length;
let midINdex = Math.floor(len / 2);
let midItem = arr.splice(midINdex, 1)[0];
let left = [];
let right = [];
for (let i = 0; i < len - 1; i++) {
if (arr[i] < midItem) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([midItem], quickSort(right));
}
5.归并排序
归并排序动图演示原理
归并排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。 将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
result.concat(leftArr).concat(rightArr):
之所以最后要进行拼接操作,是因为有可能两边数组比较后,一边还有剩余(如一边的值特小,一边特大),所以要进行拼接。
function mergeSort(array) {
if (array.length == 1) return array;
var middle = Math.floor(array.length / 2);
var left = array.slice(0, middle);
var right = array.slice(middle);
return merge(mergeSort(left), mergeSort(right));
function merge(leftArr, rightArr) {
var result = [];
while (leftArr.length > 0 && rightArr.length > 0) {
if (leftArr[0] < rightArr[0]) result.push(leftArr.shift());
else result.push(rightArr.shift());
}
return result.concat(leftArr).concat(rightArr);
}
6.计数排序
原理
理由数组下标天然排序的原理,以数组元素值为键,出现次数为值存进一个临时数组,最后再遍历这个临时数组还原回原数组。
function countSort(arr) {
//克隆原数组
let cloneArr = Array.from(arr)
// 定义一个空数组
let arr2 = [];
// 找到这个数组的 最大 最小值
let max = Math.max(...cloneArr);
let min = Math.min(...cloneArr);
// 定义一个索引 这个索引是 后面用来改变原数组的
let index = 0;
// 装桶操作
for (let i = 0, len = cloneArr.length; i < len; i++) {
// 把传入数组的值作为下标 装入一个长度为数组最大值长度的临时数组
let temp = cloneArr[i];
arr2[temp] = 1;
}
// 还原原数组
for (let i = min; i <= max; i++) {
// 如果arr2[i] > 0表示他的下标是从原数组获取的,需要进行还原操作
if(arr2[i] > 0) {
// 就把原数组的第index位赋值为 i(i本身就是从数组里获取的下标)
cloneArr[index] = i;
//赋值完成后进行+1操作,等待给原数组下一位赋值
index ++
// 每赋值完一次后 临时数组当前位的值就--,原本值为1,--就为0了,如果=0 就说明这位上没有值了,则不会进行赋值操作
arr2[i]--;
}
}
return cloneArr;
}
7.桶排序
//插入排序方法(在各个桶里调用)
function insertSort(arr) {
for (let i = 1; i < arr.length; i++) {
for (let j = i; j > 0; j--) {
if (arr[j] < arr[j - 1]) {
[arr[j - 1], arr[j]] = [arr[j], arr[j - 1]];
}
}
}
return arr;
}
//桶排序
function bucketSort(arr, bucketCount) {
let result = [];
// 找出最大值和最小值,为给每个桶分配大小做准备
let minValue = Math.min(...arr);
let maxValue = Math.max(...arr);
// 求得每个桶的大小
bucketSize = Math.ceil(maxValue / bucketCount);
//创建桶
bucket = new Array(bucketCount);
for (let i = 0; i < bucketCount; i++) {
bucket[i] = [];
}
// 往桶里放数据
for (let i = 0; i < arr.length; i++) {
bucket[Math.floor((arr[i] - minValue) / bucketSize)].push(arr[i]);
}
// 对每个桶进行单独排序,放进结果数组中
for (let i = 0; i < bucketCount; i++) {
let data = insertSort(bucket[i])
let len = data.length
if(len > 0) {
data.forEach(item => {
result.push(item)
});
}
}
return result;
}
8.希尔排序(未理解)
function shellSort(arr) {
let len = arr.length;
// gap 即为增量
for (let gap = Math.floor(len / 2); gap > 0; gap = Math.floor(gap / 2)) {
console.log("gap", gap)
for (let i = gap; i < len; i++) {
console.log('i', i)
let j = i;
let current = arr[i];
while(j - gap >= 0 && current < arr[j - gap]) {
arr[j] = arr[j - gap];
j = j - gap;
}
arr[j] = current;
}
}
}
网友评论