首先说一下,这篇文章怎么看,我是以一个我认为还不错的文章作为基础,它的特点是js写的,我原来也学过算法,当时是用c++写的,再加上我重新理解的思路,写的一篇文章,源码,我验证过,用就完啦
内容可能不是很丰满,甚至会有一些问题,我会时常的回头看一遍,再进行编辑。明眼的朋友,同时也欢迎你们的批评指正
排序总结————常见的排序
常见的9中排序(冒泡,选择,插入(二分插入,希尔),归并,快速,堆,计数,基数,桶排序)可分为两类
比较排序:冒泡,选择,插入(二分插入,希尔),归并,堆,快速
非比较排序:计数,基数,桶排序
个人见解:
1.排序有时候,很抽象,我这是这样形象化的。想象一个箱子(向上开口),放着你的快件,快件的上面的边缘,标着序列号,这时候,文件序列号是乱序的,请用下面方法排列吧!
或者现象你在档案馆整理档案、图书馆整理书籍
2.边界的问题用纸笔画一下会好理解一些
冒泡排序:(Bubble sort)
冒泡排序算法的运作如下:
1、比较相邻的元素,如果前一个比后一个大,就把它们两个调换位置。
2、对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
3、针对所有的元素重复以上的步骤,除了最后一个。
4、持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
分类 -------------- 内部比较排序
数据结构 ---------- 数组
最差时间复杂度 ---- O(n^2)
最优时间复杂度 ---- 如果能在内部循环第一次运行时,使用一个旗标来表示有无需要交换的可能,可以把最优时间复杂度降低到O(n)
平均时间复杂度 ---- O(n^2)
所需辅助空间 ------ O(1)
稳定性 ------------ 稳定
function BubbleSort(arr) {
for(var i = 0;i < arr.length-1; i++) {//控制次数
for(var j = 0;j < arr.length-1-i; j++) {//两两比较找出最大的,-i比较过的不用在比较
if(arr[j] > arr[j+1]) {
var temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
鸡尾酒排序(定向冒泡排序)
从低到高然后从高到低来回排序,性能略好于冒泡排序。(与折半查找类似)
分类 -------------- 内部比较排序
数据结构 ---------- 数组
最差时间复杂度 ---- O(n^2)
最优时间复杂度 ---- 如果序列在一开始已经大部分排序过的话,会接近O(n)
平均时间复杂度 ---- O(n^2)
所需辅助空间 ------ O(1)
稳定性 ------------ 稳定
function CocktailSort(arr) {
var left = 0;
var right = arr.length - 1;
while(left < right) {
for(var i = left;i < right;i++) {
if(arr[i] > arr[i+1]) {
var temp = arr[i];
arr[i] = arr[i+1];
arr[i+1] = temp;
}
}
right--;
for(var j = right; j > left;j--) {
if(arr[j-1] > arr[j]) {
var temp1 = arr[j-1];
arr[j-1] = arr[j];
arr[j] = temp1;
}
}
left++;
}
}
选择排序
排序原理:开始时在序列中找到一个最小的元素作为有序的序列的第一个元素,一次找次最小元素放在有序队列的末尾,以此类推知道所有元素有序为止。
与冒泡排序的区别:冒泡在每一次中两个相邻的元素对比找出大的或者小的元素,经过往复就可以达到有序。选择排序是在遍历的过程中利用当前最小元素和所有元素进行比较。
分类 -------------- 内部比较排序
数据结构 ---------- 数组
最差时间复杂度 ---- O(n^2)
最优时间复杂度 ---- O(n^2)
平均时间复杂度 ---- O(n^2)
所需辅助空间 ------ O(1)
稳定性 ------------ 不稳定
function SelectSort(arr) {
for(var i = 0;i < arr.length-1;i++) {
var min = i;//第一个元素
for(var j = i + 1; j < arr.length; j++) {//从第二个元素开始遍历
if(arr[j] < arr[min]) {//依次找到最小的元素
min = j;
}
}
if(min != i) {//放到已排序序列的末尾,该操作很有可能把稳定性打乱,选择排序是不稳定的排序算法
var temp = arr[min];
arr[min] = arr[i];
arr[i] = temp;
}
}
}
插入排序(直接插入排序)
算法描述如下:(类似一玩扑克牌的插入)
1、从第一个元素开始,该元素可以认为已经被排序
2、取出下一个元素,在已经排序的元素序列中从后向前扫描
3、如果该元素(已排序)大于新元素,将该元素移到下一位置
4、重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5、将新元素插入到该位置后
6、重复步骤2~5
function InsertSort(arr) {
for(var i = 1;i < arr.length;i++) {
var first= arr[i];//抓取一张扑克牌
var j = i-1;//另一部分牌存放总是排序好的
while(j >= 0 && arr[j] > first) {//将抓到的牌与有序的牌从右向左进行比较
arr[j + 1] = arr[j]; //如果该有序的牌比抓到的牌大,就将其右移
j--;
}
arr[j + 1] = first; //直到该手牌比抓到的牌小(或二者相等),将抓到的牌插入到该手牌右边(相等元素的相对次序未变,所以插入排序是稳定的)//此处的j+1=原来的j,在while中j--操作
}
}
插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,比如量级小于千,那么插入排序还是一个不错的选择。 插入排序在工业级库中也有着广泛的应用,在STL的sort算法和stdlib的qsort算法中,都将插入排序作为快速排序的补充,用于少量元素的排序(通常为8个或以下)。
插入排序的改进:二分插入排序(折半插入)
function BinaryInsertSort(arr) {
for(var i = 1; i < arr.length;i++) {
var first = arr[i];//抓取第一下张牌
var left = 0;
var right = i-1;//两个游标
while(left <= right) {//满足游标的不出界条件,确定抓到的新牌的位置
var mind = Math.ceil( (left + right)/2 ); //折半对比必须转化成整数,parseInt()也可以
if(arr[mind] > first) {//如果抓取的牌小于中间的则把中间的左边的位置换成最右侧的,一次这样判断
right = mind-1;
}else{
left = mind+1;
}
}
for (var j = i-1; j >= left; j--) { // 将欲插入新牌位置右边的牌整体向右移动一个单位
arr[j + 1] = arr[j];
}
arr[left] = first;//位置移动后将插入的牌插入对应的位置
}
}
开始还考虑用向上分ceil还是先下分floor,其实只要分开就好。
当n较大时,二分插入排序的比较次数比直接插入排序的最差情况好得多,但比直接插入排序的最好情况要差,所当以元素初始序列已经接近升序时,直接插入排序比二分插入排序比较次数少。二分插入排序元素移动次数与直接插入排序相同,依赖于元素初始序列。*/
插入排序的更高效改进:希尔排序(Shell Sort)
(递减增量排序插入排序的一种更高效的改进版本。希尔排序是不稳定的排序算法。)
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
1 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
2 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位
希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。
假设有一个很小的数据在一个已按升序排好序的数组的末端。如果用复杂度为O(n^2)的排序(冒泡排序或直接插入排序),可能会进行n次的比较和交换才能将该数据移至正确位置。而希尔排序会用较大的步长移动数据,所以小数据只需进行少数比较和交换即可到正确位置。
分类 -------------- 内部比较排序
数据结构 ---------- 数组
最差时间复杂度 ---- 根据步长序列的不同而不同。已知最好的为O(n(logn)^2)
最优时间复杂度 ---- O(n)
平均时间复杂度 ---- 根据步长序列的不同而不同。
所需辅助空间 ------ O(1)
稳定性 ------------ 不稳定
function ShellSort(arr) {
//var arr = [298,113,138,96,78,203,56,11];----1
//var arr = [113,298,138,96,78,203,56,11];----2
//var arr = [113,138,298,96,78,203,56,11];----2
var n = 0;
while(n <= arr.length) { //生成初始增量
n = 3*n + 1;
}//n = 1
while ( n >= 1) {
for(var i = n; i < arr.length; i++) {//i=1,i=2,i=3
var j = i - n;//j=0,j=1,j=2
var first = arr[i];//first=arr[1]=113,first=arr[2]=138,first=arr[3]=96
while (j >= 0 && arr[j] > first) {//arr[0] 与 first比较。抓到的一张牌和第一张对比,298-113, 298-138 arr[0]=113--first=138(不成立),arr[2]=298--first=96
arr[j + n] = arr[j];//大的往后移动n个位置 arr[1] =113--arr[0]=298, arr[1+1]=138--arr[1]=298,arr[2+1]=96--arr[2]=298
j = j - n;//j-n=-1;移动之后确定退出的条件 j= 0-1=-1,j=1-1=0,j=2-1
}
arr[j + n] = first;//j=1,小的做一次插入排序 arr[0+1]=first=113 arr[0+1]=138
}
n = (n - 1) / 3; // 递减增量
}
}
希尔排序是不稳定的排序算法,虽然一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱。
个人理解:
说实话,看了上面的讲解我并不明白希尔排序在干嘛。
后来看别的说的挺好,总结一下:
1.我们要分组,以相同间隔数量的人为一组(如上图,相同颜色是一组,第1,5,9)
2.间隔数量自己定,一般设为总数组长度一半。希尔排序的间隔序列不推荐用2的幂(1 2 4 8 16…),因为这样直到间隔为1之前,都不会将奇数位置与偶数位置的元素进行比较,这样是低效的。希尔自己认为可以用2的幂 - 1序列(1 3 7 15…),后来又有文章建议用3x + 1(1 4 13 40 121…)
3.每一组排序完毕,间隔数量减半,重新分组,直到间隔数为1
4.每组排序的方法:插入排序
function shellSort(arr){
var h = 1;
var n = arr.length;
while(h < Math.floor(n/3)){
h = 3 * h + 1;
}
while(h >= 1){
for(let i = h; i < n; i++){
let temp = arr[i];
let j;
for(j = i; j>=h && temp < arr[j-h];j-=h){
arr[j] = arr[j - h];
}
arr[j] = temp;
}
h = (h-1)/3;
}
console.log(arr);
}
归并排序
由计算机之父冯·诺伊曼提出
真是费了不少劲,接下来发挥你的现象力,跟我我的思路,听我娓娓道来。
0.排序可以现象成一个人在整理杂序的档案。
1.为了整理速度更快,后来就又来了一个人,这时候,就一人整理一半(利用递归方法 二分,这是归并的开始)。
2.按照上面的思路,那来更多的人就整理的更快啦,按道理是这样的,但是如果一直二分下去,一人到最后只整理两本,不说人够不够(人相当于内存),关键是违背一开始初衷,反而效率更慢啦,那怎么找到这个平衡点那,最终通过长期的试错后,发现一个人用A方法(插入排序)整理15个档案最快,这下问题解决啦
3.但新的问题又来啦,每个人都整理好啦,那每个人怎么跟别人合并自己的档案那。办法是这样的,临近两个人的活,交给一个人(递归的到底啦,往回走),因为之前每个人都把自己档案整理好啦,所以用B方法(__merge()整理近乎有序的数列最快)整理是最快的,所以这一步一般都用这个方法,这个人整理好啦,再交给临近人处理,最后就剩一个人啦
4.工作完成记得点赞。嘻嘻嘻
/**
* 归并排序
* 下面为升序
* 缺点:在递归时,会耗费大量内存空间,但时间上非常快
*/
var arr = [298,113,138,96,78,203,56,11,298,113,138,96,78,203,56,11,298,113,138,96,78,203,56,11];
//将arr[l...mid]和arr[mid+1...r]两部分进行归并(对近乎有序的数列进行处理)
function __merge(arr, l, mid, r){
var aux = [];
for(let i = l; i <= r; i++){
aux[i-l] = arr[i];
}
// 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
var i = l, j = mid + 1;
for(let k = l; k < r; k++){
if (i > mid) { // 如果左半部分元素已经全部处理完毕
arr[k] = aux[j - l]; j++;
}
else if (j > r) { // 如果右半部分元素已经全部处理完毕
arr[k] = aux[i - l]; i++;
}
else if (aux[i - l] < aux[j - l]) { // 左半部分所指元素 < 右半部分所指元素
arr[k] = aux[i - l]; i++;
}
else { // 左半部分所指元素 >= 右半部分所指元素
arr[k] = aux[j - l]; j++;
}
}
}
function insertionSort(arr,l,r){
for(var i = l+1; i <= r; i++){
let temp = arr[i];
let j;
for(j = i; j > l && arr[j-1] > temp; j--){
arr[j] = arr[j-1];
}
arr[j] = temp;
}
console.log("insert: " + arr);
}
function __mergeSort(arr, l, r){
if(r-l <=15){//优化二:在小范围的排序中插入排序占有很大优势
insertionSort(arr, l, r);
return;
}
let mid = Math.floor((l+r)/2);
console.log(l,mid,r);
__mergeSort(arr, l, mid);
__mergeSort(arr, mid + 1, r);
if(arr[mid] > arr[mid + 1]){//优化一:近乎有序数组加这个判断,提前终止交换
__merge(arr, l, mid, r)
}
}
function mergeSort(arr){
var n = arr.length;
__mergeSort(arr, 0, n-1);
return arr;
}
console.log(mergeSort(arr));
就是JavaScript没有对递归进行优化。运用递归函数不仅没有运行速度上的优势,还可能造成程序运行失败。因此不建议使用递归。
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(n log n)的时间复杂度。代价是需要额外的内存空间。
function mergeSort(arr) { //采用自上而下的递归方法
var len = arr.length;
if(len < 2) {
return arr;
}
var middle = Math.floor(len / 2),//进行分割
left = arr.slice(0, middle),
right = arr.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {//
var result = [];
while (left.length>0 && right.length>0) {
if(left[0] <= right[0]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
while (left.length) {
result.push(left.shift());
}
while (right.length) {
result.push(right.shift());
}
return result;
}
console.log(mergeSort([1, 3, 4, 2, 5, 0, 8, 10, 4]));
//var arr1 = mergeSort(arr);
console.log( mergeSort([298,113,138,96,78,203,56,11]) );
console.log( mergeSort([98,113,138,96,78,23,56,11]) );
console.log( mergeSort([908,1130,138,196,78,203,56,11]) );
console.log( mergeSort([8,203,13,16,78,201,56,11]) );
console.log( mergeSort([8,203,13,16,78,79,156,199]) );
/*setTimeout(function() {
testMap(arr1);
console.log(arr1);
},1500);*/
上面的例子也能实现自下而上的归并但是依然用了递归的范式,显然不对
自下而上的归并,优点就是不需要递归开辟空间,接下来看我的
var arr = [298,113,138,96,78,203,56,11,298,113,138,96,78,203,56,11,298,113,138,96,78,203,56,11];
function merge(arr, l, mid, r){
var aux = [];
for(let i = l; i <= r; i++){
aux[i-l] = arr[i];
}
// 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
var i = l,j = mid + 1;
//重点理解下面的意义。前两个if首先确定无论是左边区还是右半区都是没有超界的,才有后面的对比
for(let k = l; k <= r; k++){
if (i > mid) { // 如果左半部分元素已经全部处理完毕,接下来只处理右边的没有归并的
arr[k] = aux[j - l]; j++;
}
else if (j > r) { // 如果右半部分元素已经全部处理完毕,接下来只处理左边的没有归并的
arr[k] = aux[i - l]; i++;
}
else if (aux[i - l] < aux[j - l]) { // 左半部分所指元素 < 右半部分所指元素
arr[k] = aux[i - l]; i++;
}
else { // 左半部分所指元素 >= 右半部分所指元素
arr[k] = aux[j - l]; j++;
}
}
}
//sz 间隔空间
function mergeSortBU(arr){
var n = arr.length;
for(var sz = 1;sz < n; sz += sz){
for(var i = 0; i+sz < n;i += sz + sz){
console.log(i,sz,arr,i + sz - 1,Math.min(i+sz+sz-1,n-1));
merge(arr, i, i + sz - 1, Math.min(i+sz+sz-1,n-1));
}
}
return arr
}
console.log(mergeSortBU(arr));
快速排序(Quick Sort)
又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高! 它是处理大数据最快的排序算法之一了。虽然Worst Case的时间复杂度达到了O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为O(n log n) 的排序算法表现要更好,快速排序的最坏运行情况是O(n²),比如说顺序数列的快排。但它的平摊期望时间是O(n log n) ,且O(n log n)记号中隐含的常数因子很小,比复杂度稳定等于O(n log n)的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。
快速排序的内循环比大多数排序算法都要短小,这意味着它无论是在理论上还是在实际中都要更快。它的主要缺点是非常脆弱,在实现时要非常小心才能避免低劣的性能。
快速排序使用分治策略(Divide and Conquer)来把一个序列分为两个子序列。步骤为:
思路,思路,思路
1、从序列中挑出一个元素,作为"基准"(pivot).
2、把所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准的后面(相同的数可以到任一边),这个称为分区(partition)操作。
对每个分区递归地进行步骤1~2,递归的结束条件是序列的大小是0或1,这时整体已经被排好序了。
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
快速排序(基础版)(看标题就知道还有升级版)
下面是我原来用C++实现的方法,现在用js写一遍,借鉴的文章有点不靠谱,接下来大家跟着我的思路来,走起:
先看图说话
接下来阐述一下这里面的思想:
1.我阐述一下几个关键词:
(1)v是被选定的基准元素,对后面的元素进行比较,比v小的放在前面,比v大的放在后面。
(2)j是比v小的最后一个元素,后面都是比v大的
(3)e为当前被比较的值
(4)partition操作:完成一次将整个数组分为左边(比v小)和右边(比v大)的操作
2.思路(看上面,黑体,“思路,思路,思路”)
3.我经常犯的错误就是没想明白,你被分的数组边界在哪,是开区间,还是闭区间
上 码
var arr = [298,113,138,96,78,203,56,11,298,113,138,96,78,203,56,11,298,113,138,96,78,203,56,11];
function partition(arr, l, r){
var p = arr[l];
var j = l;
// var e = l + 1;
for(let i = l+1;i <= r; i++){
// <p的部分
if(arr[i]<p){
let temp = arr[i];
arr[i] = arr[j+1];
arr[j+1] = temp;
j++;
}
// >p的部分操作 不用操作,直接当前被比较的值被向后移一位就行
}
let temp = arr[l];
arr[l] = arr[j];
arr[j] = temp;
return j;
}
//为内部函数
function quickSortX(arr, l, r){
if(l >= r) return;
var p = partition(arr, l, r);
quickSortX(arr, l, p-1);
quickSortX(arr, p+1, r);
}
//快速排序
function quickSort(arr){
var n = arr.length;
quickSortX(arr, 0, n-1);
return arr;
}
console.log(quickSort(arr));
快速排序(优化版)(又名:随机快速排序)
优化
1.沿用之前思路,在小范围(一般为15个元素),使用插入排序
2.先说问题,通过测试对归并排序与快速排序,
测试条件1:近乎有序数组
结果:归并排序/快速排序=100多倍
测试条件2:无序数组
结果:归并排序/快速排序=0.x倍
测试条件1的结果,不能被接受的的,接下来分析原因:
(1)归并排序每次递归时平分了俩个数组;快速排序因为每次都是取第一个数,在近乎有序的数组中,就会在递归时候,分成一大、一小两个部分。(看图说话)
上图为一般情况
上图为最坏情况
解决方法:不取第一个进行比较而是随机数组中一个值,作为基准值进行比较
上 码
//快速排序升级版(随机算法)
var arr = [298,113,138,96,78,203,56,11,298,113,138,96,78,203,56,11,298,113,138,96,78,203,56,11];
function insertionSort(arr,l,r){
for(var i = l+1; i <= r; i++){
let temp = arr[i];
let j;
for(j = i; j > l && arr[j-1] > temp; j--){
arr[j] = arr[j-1];
}
arr[j] = temp;
}
// console.log("insert: " + arr);
}
function partition(arr, l, r){
var randomNum = Math.round(Math.random()*(r-l)+l);
var tempz = arr[l];
arr[l] = arr[randomNum];
arr[randomNum] = tempz;
var p = arr[l];
var j = l;
// var e = l + 1;
for(let i = l+1;i <= r; i++){
// <p的部分
if(arr[i]<p){
let temp = arr[i];
arr[i] = arr[j+1];
arr[j+1] = temp;
j++;
}
// >p的部分操作 不用操作,直接当前被比较的值被向后移一位就行
}
let temp = arr[l];
arr[l] = arr[j];
arr[j] = temp;
return j;
}
//为内部函数
function quickSortX(arr, l, r){
// if(l >= r) return;
if(r-l <= 15){
insertionSort(arr, l, r);
return;
}
var p = partition(arr, l, r);
quickSortX(arr, l, p-1);
quickSortX(arr, p+1, r);
}
//快速排序
function quickSort(arr){
var n = arr.length;
quickSortX(arr, 0, n-1);
return arr;
}
console.log(quickSort(arr));
双路快速排序
优化的点
改进优化是因为出现了问题。在元素数量为100万,随机范围为[0,10],随机快速排序慢到无法接受,那是什么原因那?
看上图看出什么问题?
如果在大量存在重复键值的数组中,在单次partition操作时,存在大量与v相同的键值,如果已经结束,虽然这是分好区域,但是与v相等的键值依然要进行下一次的partition操作,造成无意义的操作
接下来换一种思路去解决这个问题
1.用双向(一头一尾),或者说,分而治之的思想,
2.先从左边开始,如果比v小,i++。如果遇到比v大的值,就停止操作,进入第3步
3.从右边开始,比v大,j–。如果遇到比v小的值,就停止操作,交换 i 指向的值与 j 指向的值,i++,j–
4.原来聚集在一边的问题现在分到了两边
5.过程中e一直是要与v比较的值
6.当i>j时,结束Partition操作,将arr[l]与arr[j]值交换
上 码
var arr = [298,113,138,96,78,203,56,11,298,113,138,96,78,203,56,11,298,113,138,96,78,203,56,11];
//交换函数
function swap(a, b){
var temp = a;
a = b;
b = temp;
return [a,b]
}
function insertionSort(arr,l,r){
for(var i = l+1; i <= r; i++){
let temp = arr[i];
let j;
for(j = i; j > l && arr[j-1] > temp; j--){
arr[j] = arr[j-1];
}
arr[j] = temp;
}
// console.log("insert: " + arr);
}
function partition(arr, l, r){
var randomNum = Math.round(Math.random()*(r-l)+l);
[arr[randomNum],arr[l]] = swap(arr[randomNum], arr[l]);
var p = arr[l];
var i = l + 1,j = r;
while(true){
while(i <= r && arr[i] < p) i++;
while(j >= l + 1 && arr[j] > p) j--;
if( i > j) break;
[arr[i], arr[j]] = swap(arr[i], arr[j]);
i ++;
j --;
}
[arr[l], arr[j]] = swap(arr[l], arr[j]);
return j;
}
//为内部函数
function quickSortX(arr, l, r){
// if(l >= r) return;
if(r-l <= 15){
insertionSort(arr, l, r);
return;
}
var p = partition(arr, l, r);
quickSortX(arr, l, p-1);
quickSortX(arr, p+1, r);
}
//快速排序
function quickSort(arr){
var n = arr.length;
quickSortX(arr, 0, n-1);
return arr;
}
console.log(quickSort(arr));
三路快速排序
小于:less than 大于:greater than
改进地方
简单说就是双路排序的plus版,主要还是针对有大量重复键值的存在情况
意义解释:
l:起始的第一个元素
lt:<v部分最后一个元素索引
gt:>v部分第一个元素索引
r:数组最后一个元素索引
i:当前要比较的元素索引
1.从头到尾遍历数组
2.三种情况:(1).<V
1.交换i的元素与lt+1的元素 2.<v边界向后移一位
(2).>V
1.交换i的元素与gt-1的元素 2.>v边界的向前移一位
(3).==V
1.i++
3.交换l位置上元素与lt位置上元素交换
4.将<v,>v部分进行3路排序
上 码
//3路快速排序
var arr = [298,113,138,96,78,203,56,11,298,113,138,96,78,203,56,11,298,113,138,96,78,203,56,11];
//交换函数
function swap(a, b){
var temp = a;
a = b;
b = temp;
return [a,b]
}
function insertionSort(arr,l,r){
for(var i = l+1; i <= r; i++){
let temp = arr[i];
let j;
for(j = i; j > l && arr[j-1] > temp; j--){
arr[j] = arr[j-1];
}
arr[j] = temp;
}
// console.log("insert: " + arr);
}
function __quickSort3Ways(arr, l, r){
if(r - l <= 15){
insertionSort(arr, l, r);
return;
}
//partition
const randomNum = Math.round(Math.random()*(r - l)+l);
[arr[randomNum],arr[l]] = swap(arr[randomNum], arr[l])
var v = arr[l];
var lt = l;
var gt = r + 1;
var i = l + 1;
while(i < gt){
if(arr[i] < v){
[arr[i], arr[lt+1]] = swap(arr[i], arr[lt+1]);
lt++;
i++;
}
else if(arr[i] > v){
[arr[i], arr[gt-1]] = swap(arr[i], arr[gt-1]);
gt--;
}
else{
i++
}
}
[arr[l], arr[lt]] = swap(arr[l], arr[lt]);
__quickSort3Ways(arr, l, lt - 1);
__quickSort3Ways(arr, gt, r);
}
function quickSort3Ways(arr){
var n = arr.length;
__quickSort3Ways(arr, 0, n-1);
console.log(arr);
}
quickSort3Ways(arr);
TODO 下面的内容没有整理完 下面的内容没有整理完 下面的内容没有整理完
堆排序
1.大顶堆:最大堆中的最大元素值出现在根结点(堆顶)
堆中每个父节点的元素值都大于等于其孩子结点(如果存在)
2.小顶堆:最小堆中的最小元素值出现在根结点(堆顶)
堆中每个父节点的元素值都小于等于其孩子结点(如果存在)
3.堆排序原理
堆排序就是把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。在堆中定义以下几种操作:
1)、最大堆调整(Max-Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
2)、创建最大堆(Build-Max-Heap):将堆所有数据重新排序,使其成为最大堆
3)、堆排序(Heap-Sort):移除位在第一个数据的根节点,并做最大堆调整的递归运算
继续进行下面的讨论前,需要注意的一个问题是:数组都是 Zero-Based,这就意味着我们的堆数据结构模型要发生改变
(Zero-Based)
相应的,几个计算公式也要作出相应调整:
Parent(i) = floor((i-1)/2),i 的父节点下标
Left(i) = 2i + 1,i 的左子节点下标
Right(i) = 2(i + 1),i 的右子节点下标
最大堆调整(MAX‐HEAPIFY)的作用是保持最大堆的性质,是创建最大堆的核心子程序
console.log(heapSort(arr));
function heapSort(array) {
function swap(array, i, j) {
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
function maxHeapify(array, index, heapSize) {
var iMax,
iLeft,
iRight;
while (true) {
iMax = index;
iLeft = 2 * index + 1;
iRight = 2 * (index + 1);
if (iLeft < heapSize && array[index] < array[iLeft]) {
iMax = iLeft;
}
if (iRight < heapSize && array[iMax] < array[iRight]) {
iMax = iRight;
}
if (iMax != index) {
swap(array, iMax, index);
index = iMax;
} else {
break;
}
}
}
function buildMaxHeap(array) {
var i, iParent = Math.floor(array.length / 2) - 1;
for (i = iParent; i >= 0; i--) {
maxHeapify(array, i, array.length);
}
}
function sort(array) {
buildMaxHeap(array);
for (var i = array.length - 1; i > 0; i--) {
swap(array, 0, i);
maxHeapify(array, 0, i);
}
return array;
}
return sort(array);
}