冒泡排序
过程:
从左到右两两比较和交换, 将最大值/最小值移动到(冒泡)到右界上. 每次比较完成后将右界向前移动一位直到右届移动到第一位.
代码:
/**
* @param {number[]} nums
* @return {number[]}
*/
var bubbleSort = function(nums) {
for (let i = 0; i < nums.length - 1; i++) {
for (let j = 0; j < nums.length - 1 - i; j++) {
if (arr[j] > arr[j+1]) {
//如果左边的数大于右边的数,则交换,保证右边的数字最大
[arr[j], arr[j+1]] = [arr[j+1], arr[j]];
}
}
};
最外层的 for 循环每经过一轮,剩余数字中的最大值就会被移动到当前轮次的最后一位,中途也会有一些相邻的数字经过交换变得有序。总共比较次数是 (n−1)+(n−2)+(n−3)+…+1。
这种写法相当于相邻的数字两两比较,并且规定:“谁大谁站右边”。经过 n−1轮,数字就从小到大排序完成了。整个过程看起来就像一个个气泡不断上浮,这也是“冒泡排序法”名字的由来。
优化
每轮比较开始前进行判断,如果上一轮(i-1)比较中没有发生过交换,则立即停止排序,因为此时剩余数字,从左边界(0)到右边界(len-1-i),一定已经有序了
代码:
/**
* @param {number[]} nums
* @return {number[]}
*/
var bubbleSort = function(nums) {
let sorted = false
for (let i = 0; i < nums.length - 1; i++) {
if (sorted) break
sorted = true
for (let j = 0; j < nums.length - 1 - i; j++) {
if (arr[j] > arr[j+1]) {
//如果左边的数大于右边的数,则交换,保证右边的数字最大
[arr[j], arr[j+1]] = [arr[j+1], arr[j]];
sorted = false
}
}
};
稳定性
稳定, 只有左边的数字大于右边的数字时才会发生交换,相等的数字之间不会发生交换. 例子,
[2^,2^^,1] -> [2^,1,2^^] -> [1,2^,2^^]
选择排序
过程:
从左到右双重循环遍历数组,每经过一轮比较,找到最小元素的下标,将其交换到首位.
代码:
/**
* @param {number[]} nums
* @return {number[]}
*/
var sortArray = function(nums) {
let len = nums.length
for (let i = 0; i < len - 1; i++) {
let minIdx = i
for (let j = i + 1; j < len; j++) {
if (nums[j] < nums[minIdx]) {
//记录这轮比较中最小值的下标
minIdx = j
}
//将这轮中的最小值移动到左边界, 左界+1
[nums[i], nums[minIdx]] = [nums[minIdx], nums[i]];
}
}
};
选择排序就好比第一个数字站在擂台上,大吼一声:“还有谁比我小?”。剩余数字来挨个打擂,如果出现比第一个数字小的数,则新的擂主产生。每轮打擂结束都会找出一个最小的数,将其交换至首位。经过 n-1 轮打擂,所有的数字就按照从小到大排序完成了。
稳定性
不稳定, 最小值和首位交换的过程可能会破坏稳定性. 例子, [2^,2^^,1] -> [1,2^^,2^] 在第一次交换中原序列里的两个2的相对顺序被颠倒了.
稳定性的意义
稳定性只在一种情况下有意义:当要排序的内容是一个对象的多个属性,且其原本的顺序存在意义时,如果我们需要在二次排序后保持原有排序的意义,就需要使用到稳定性的算法。
举个例子,如果我们要对一组商品排序,商品存在两个属性:价格和销量。当我们按照价格从高到低排序后,要再按照销量对其排序,这时,如果要保证销量相同的商品仍保持价格从高到低的顺序,就必须使用稳定性算法。 当然,算法的稳定性与具体的实现有关。在修改比较的条件后,稳定性排序算法可能会变成不稳定的。如冒泡算法中,如果将「左边的数大于右边的数,则交换」这个条件修改为「左边的数大于或等于右边的数,则交换」,冒泡算法就变得不稳定了。同样地,不稳定排序算法也可以经过修改,达到稳定的效果
插入排序
过程:
插入排序和打扑克过程中的抓拍理排很像. 每次摸一张牌,就将它插入手上已有的牌中合适的位置,逐渐完成整个排序。
两种写法:
交换法:新数字插入前,不断与前面的数字交换,直到找到属于自己的位置。
移动法:新数字插入前,与前面的数字不断比较,前面的数字不断向后挪出位置,找到自己的位置后,插入一次即可。
代码:
交换法:
/**
* @param {number[]} nums
* @return {number[]}
*/
var sortArray = function(nums) {
const len = nums.length
// 从第二个数开始,往前插入数字
//当数字少于两个时,不存在排序问题,当然也不需要插入
for (let i = 1; i < len; i++) {
let j = i // j 记录当前数字下标
//当前数字比前一个数字小,则将当前数字与前一个数字交换
while (nums[j] < nums[j-1] && j > 0) {
[nums[j], nums[j-1]] = [nums[j-1], nums[j]];
// 更新当前数字下标
j--
}
}
return nums
};
整个过程就像是已经有一些数字坐成了一排,这时一个新的数字要加入,这个新加入的数字原本坐在这一排数字的最后一位,然后它不断地与前面的数字比较,如果前面的数字比它大,它就和前面的数字交换位置。
移动法:
/**
* @param {number[]} nums
* @return {number[]}
*/
var sortArray = function(nums) {
const len = nums.length
let sortedNums = new Array(len).fill(0)
sortedNums[0] = nums[0]
// 从第二个数开始,往前插入数字
for (let i = 1; i < len; i++) {
let j = i - 1
let newNum = nums[i]
while (j >= 0 && newNum < sortedNums[j]) {
//寻找插入位置的过程中不断地将比newNum大的数字向后挪
sortedNums[j+1] = sortedNums[j]
j--
}
// 两种情况会跳出循环:
//1.遇到一个<=newNum的数字,newNum就坐到它后面。
//2.已经走到数列头部,仍然没有遇到<=newNum的数字,此时j=-1,newNum就坐到数列头部。
sortedNums[j+1] = newNum
}
return sortedNums
};
整个过程就像是已经有一些数字坐成了一排,这时一个新的数字要加入,所以这一排数字不断地向后腾出位置,当新的数字找到自己合适的位置后,就可以直接坐下了。重复此过程,直到排序结束
稳定性
稳定,不会破坏原有数组中相同关键字的相对次序.
8种常见排序算法稳定性
8种常见排序(快排、希尔、堆排、归并、冒泡、选择、插入、基数排序)中,快些(希尔)选堆不稳定,剩下的稳定