持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情
排序算法
插入排序
1.直接插入排序
遍历arr,比较当前值arr[i]与之前的已排序区域arr[0]~arr[i-1]的值,进行插入。
平均时间复杂度:O(n^2)
空间复杂度:O(1)
2.希尔排序
通过比较相距一定间隔的元素来进行,各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止,也叫缩小增量排序。
平均时间复杂度:O(n^(1.3-2))
空间复杂度:O(1)
选择排序
1.直接选择排序
遍历arr,每趟挑选出最小(最大)的值。
平均时间复杂度:O(n^2)
空间复杂度:O(1)
2.堆排序
1.可以想象成完全二叉树。
2.从最后的非叶子节点开始,通过一个自下而上,自右向左比较,造成一个大根堆。将顶端元素和末尾元素交换,即选择出最大元素。
4.进行剩余元素的排序即(步骤2+步骤3)。
平均时间复杂度:O(nlog2n)
空间复杂度:O(1)
附上代码:
var sortArray = function (nums) {
const len = nums.length;
// 先根据数组,形成一个最大堆
// 从第一个非叶子节点开始,其元素位置为Math.floor(len / 2) - 1
for (let i = Math.floor(len / 2) - 1; i >= 0; i--) {
maxHeapify(nums, i, len);
}
// 提取堆顶元素,进行剩余元素的排序
for (let i = len - 1; i > 0; i--) {
// 交换堆顶元素至尾部
swap(nums, i, 0);
maxHeapify(nums, 0, i);
}
return nums;
};
// 形成最大堆操作
function maxHeapify(nums, index, len) {
let mid = index;
// 当前节点的左节点位置为 i*2+1
let i = index * 2 + 1;
// 当前节点的右节点位置为 i*2+2
let j = index * 2 + 2;
if (i < len && nums[index] < nums[i]) {
index = i;
}
if (j < len && nums[index] < nums[j]) {
index = j;
}
// 如果需要进行交换操作
if (mid !== index) {
// 进行交换,提取最大值至顶部
swap(nums, mid, index);
// 对被交换的元素进行处理
maxHeapify(nums, index, len);
}
}
// 交换操作
function swap(nums, i, j) {
const temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
交换排序
冒泡排序
遍历arr长度-1次,通过前后两两交换,每轮可以得到最大的数。
平均时间复杂度:O(n^2)
空间复杂度:O(1)
快速排序
通过先治再分的操作,取出一个元素作为pivot基准值,划分出大于基准值和小于基准值的区域,再对两个区域进行同样操作。
平均时间复杂度:O(nlog2n)
空间复杂度:O(1)
附上代码:
var sortArray = function (nums) {
quickSort(nums, 0, nums.length - 1)
return nums
};
// 相当于二叉树前序遍历
function quickSort(arr, left, right) {
// 如果left大于right 无需分治
if (left >= right) {
return
}
// 取得基准元素位置
let mid = partition(arr, left, right)
// 对基准值左侧区域进行排序
quickSort(arr, left, mid - 1)
// 对基准值右侧区域进行排序
quickSort(arr, mid + 1, right)
}
// 分治处理
function partition(nums, left, right) {
// 默认左值为基准值
const pivot = nums[left]
// 进行”填坑“操作,先从最右侧开始
while (left < right) {
// 如果右侧元素值大于基准值,向左寻找
while (left < right && nums[right] >= pivot) {
right--;
}
// 如果右侧元素值小于基准值,向left指向进行”填坑“,第一次填坑,默认是覆盖基准元素
if (left < right && nums[right] < pivot) {
nums[left] = nums[right];
}
// 如果左侧元素值小于基准值,向右寻找
while (left < right && nums[left] <= pivot) {
left++;
}
// 如果左侧元素值大于基准值,向right指向进行”填坑“。
if (left < right && nums[right] < pivot) {
nums[right] = nums[left];
}
}
// 此时left等于right,left的坑位为空,将基准值放入
nums[left] = pivot;
return left;
}
暂无分类
归并排序
通过先分后治的操作,先分解出一个个最小子集,对最小子集进行排序处理,再对2个有序的最小子集进行合并排序,重复这个操作,最后可以合并成最大集合。
平均时间复杂度:O(nlog2n)
空间复杂度:O(n)
附上代码:
var sortArray = function (nums) {
mergeSort(nums, 0, nums.length - 1);
return nums;
};
// 相当于二叉树后序遍历
function mergeSort(arr, left, right) {
// 如果left大于right,无需分治
if (left >= right) {
return;
}
// 取出中间元素进行划分区域
const mid = Math.floor((right - left) / 2) + left;
// 治理左边子集
mergeSort(arr, left, mid);
// 治理右边子集
mergeSort(arr, mid + 1, right);
// 治理当前子集:
// l指向已经有序的左边子集的开头、r指向已经有序的右边子集的开头
let l = left;
let r = mid + 1;
// 存放此子集的排序结果
const temp = [];
// 因为子集都有序,直接循环,取相对小的值插入排序结果中。
while (l <= mid && r <= right) {
if (arr[l] < arr[r]) {
temp.push(arr[l]);
l++;
} else {
temp.push(arr[r]);
r++;
}
}
// 防止左边子集个数大于右边子集个数,依次插入左边的剩余元素
while (l <= mid) {
temp.push(arr[l]);
l++;
}
// 防止右边子集个数大于右边子集个数,依次插入右边的剩余元素
while (r <= right) {
temp.push(arr[r]);
r++;
}
// 最后将此次排序结果,插入最终的结果集中
arr.splice(left, right - left + 1, ...temp);
}