写在前面
今年5月份打算参加中级软考职称,力争在上海搞一个户口,这也是没办法的事情,毕竟自己不是211,985高校人才,只能靠自己一步一个脚印,另辟蹊径了。软考对数据结构和算法的要求还是蛮基础的,越基础知识往往越容易忽略。经历过多年的开发洗礼之后,相信大部分人都会跟我一样,最后能让你产生价值的东西不是做了多少项目,而是基础提高,有没有将项目汇总形成自己的一套方法论。
查看了历年的真题,发现竟然对排序算法的稳定性考察了很多次,而我竟然对稳定性这个概念一直很模糊,大学期间也可能只是过过眼,没太留意。趁现在弄明白单独写出来,相信对有同命相连的朋友也有帮助。
稳定性的概念?
稳定性是在一组未排序的序列中,如果存在两个相等的元素A和B,且在未排序序列中,A在B前面位置,如果在排序结束之后,A、B的位置没有发生变化,也就是说A仍然在B的前面,那么就称该排序算法是稳定的。
案例分析
这里通过冒泡和快速两种排序算法对稳定性进行说明;
冒泡排序思想:在未排序序列中,两个相邻的元素进行比较,如果互为逆序,就交换他们的值,并对每一对相邻元素做相同处理,直到最后一个元素为止。这一轮做完之后,最后一个元素将是序列中的最大值(或者最小值),继续对剩余未排序序列重新以上的步骤,直到排序完毕。
快速排序思想:通过一趟排序将待排序的序列划分为独立的两部分,即前半区和后半区,其中,前半区中的序列元素均不大于后半区的序列元素,之后再分别对这两个半区的序列进行快速排序,直到排序有序为止。
定义一个未排序的序列:
let arr = [6,7*,3,7,4,5];
我给arr[1]赋值成了7*,只是为了方便下面说明,在实际开发过程中肯定不能这么用。
根据冒泡排序思想,做增序排序:
第一轮:
6,7*比较,7*>6,不交换值,序列不变;
7*,3比较,3<7*,交换值,此时序列变成[6,3,7*,7,4,5];
7*,7比较,值相等不交换,序列不变;
……
第一轮结束之后,序列变成[6,3,7*,4,5,7];
同理,第二轮结束之后,序列变成[3,6,4,5,7*,7];
最后,第三轮结束之后,序列变成[3,4,5,6,7*,7],即是已排序好的序列;
可以看出来,7*和7的相对位置并没有发生变化,说明冒泡排序是一种稳定排序。
根据快速排序思想,也做增序排序:
快速排序中有两个重要的概念:指示变量和枢轴关键字,其中枢轴关键字作为一趟快速排序过程的前后半区的划分,通常是序列中的第一个元素。
具体做法是:设置两个指示变量,初始值分别指向未排序序列中的第一个元素和最后一个元素。设置枢轴关键字为,首先从j位置向前检索,找到第一个小于的元素时将该元素向前移动到位置,之后从位置向后检索,找到第一个大于的元素时将该元素向后移动到位置,重复这个过程直到和相等为止。
还是用上面已定的未排序序列let arr = [6,7*,3,7,4,5],初始化pivot=6,i=0,j=5;
第一轮检索:
向前检索,j--,j=5时,发现arr[5]<pivot;i,j位置交换之后,即arr[0]=arr[5],序列变成[5,7*,3,7,4,5];
向后检索,i++,i=1时,发现arr[1]>pivot;i,j位置交换之后,即arr[5]=arr[1],序列变成[5,7*,3,7,4,7*];
此时,,继续第二轮检索:
向前检索,j--,j=4时,发现arr[4]<pivot;i,j位置交换之后,即arr[1]=arr[4],序列变成[5,4,3,7,4,7*];
向后检索,i++,i=3时,发现arr[3]>pivot;i,j位置交换之后,即arr[3]=arr[4],序列变成[5,4,3,7,7,7*];
此时,,继续第三轮检索:
向前检索,j--,j=3时,发现,此时,第一趟快速排序已经结束,将pivot插入i位置,最后序列变成[5,4,3,6,7,7*];
以上只模拟了第一趟快速排序,前后半区被划分成了[5,4,3]和[6,7,7*]。只需第一趟其实就已经看出来了,快速排序算法是一种不稳定的排序算法,因为7*在第一趟排序完成之后竟然跑到了7的后面,也就是说,位置排序前后相对位置发生了变化。
综合比较其他排序算法,对稳定性做了如下的对比汇总表:
当然排序算法远远不止以上8种,但都会离不开稳定性这个概念。
附上冒泡排序和快速的排序的代码,结合代码学习更加高效快捷。
冒泡排序:
// 冒泡排序
function sortMP() {
let arr = [6, 2, 3, 2, 4, 5];
// console.log(arr, arr.length)
// let arr1 = [1, 2, 4, 3, 5];
for (let i = 0; i < arr.length - 1; i++) { // 第一个循环 n*n, arr.length*arr.length;
let flag = false; // 判断是否已经提前排序完成了?
for (let k = 0; k < arr.length - 1 - i; k++) { // 第二个循环
let temp = 0;
if (arr[k] > arr[k + 1]) {
temp = arr[k];
arr[k] = arr[k + 1];
arr[k + 1] = temp;
flag = true;
}
}
if (!flag) { // 排序已经提前结束
break;
}
}
console.log(arr);
}
快速排序:
// 一趟排序过程
function partition(arr, low, high) {
let i, j;
let piovt = arr[low]; // 枢轴取第一个元素
i = low;
j = high;
while (i < j) {
while (i < j && arr[j] >= piovt) j--; // 从后往前检索
arr[i] = arr[j];
while (i < j && arr[i] <= piovt) i++; // 从前往后检索
arr[j] = arr[i];
}
arr[i] = piovt; // 一趟快速排序完毕,将枢轴归位
return i;
}
// 快速排序算法
function quickSort(arr, low, high) {
if (low < high) {
let mid = partition(arr, low, high);
quickSort(arr, low, mid - 1); // 前半区快速排序
quickSort(arr, mid + 1, high); // 后半区快速排序
}
}
总结
- 稳定性在排序算法中占有重要地位,以上只是针对整数序列的简单说明,如果涉及到复杂对象序列的排序,针对某个关键属性进行排序,如果不稳定排序,可能会跟期望的不一致。
- 对于排序算法核心思想理解以前是一个混乱无章的存在,针对稳定性系统学习,终于理清了8大典型算法的核心思路。别人1分钟就能明白的事情,我可能要多花1个小时才吃透。