Javascript 数组自定义排序,并获取排序后的保存原索引的同序数组(堆排序实现)
比如数组A:
[
0: 5,
1: 2,
2: 4,
3: 3,
4: 1
]
排序后的结果为:[1, 2, 3, 4, 5],但是有时候会有需求想要保留排序前的位置到一个同位数组里,如前例则为:[4, 1, 3, 2, 0],因此就利用堆排序写了一个单独的数组排序过程加以实现。
代码如下:
function arrayKeys(arr) {
var i = 0,
len = arr.length,
keys = [];
while (i < len) {
keys.push(i++);
}
return keys;
}
// 判断变量是否为数组
function isArray(arr) {
return ({}).toString.call(arr).match(/^\[[^\s]+\s*([^\s]+)\]$/)[1] == 'Array';
}
// 堆排序
function heapSort(arr, keys, order) {
if (!isArray(arr) || !isArray(keys)) return ;
var order = (order + '').toLowerCase() == 'desc' ? order : 'asc';
// 交换位置
function changePos(arr, cur, left) {
var tmp;
tmp = arr[cur];
arr[cur] = arr[left];
arr[left] = tmp;
}
// 构造二叉堆
function heap(arr, start, end, isMax) {
var isMax = isMax == undefined ? true : isMax, // 是否最大堆,否为最小堆
cur = start, // 当前节点的位置
left = 2 * cur + 1; // 左孩子的位置
for (; left <= end; cur = left, left = 2 * left + 1) {
// left是左孩子,left + 1是右孩子
if (left < end && ((isMax && arr[left] < arr[left + 1]) || (!isMax && arr[left] > arr[left + 1]))) {
left++; // 左右子节点中取较大/小者
}
if ((isMax && arr[cur] >= arr[left]) || (!isMax && arr[cur] <= arr[left])) {
break;
} else {
// 原index跟随排序同步进行
changePos(keys, cur, left);
changePos(arr, cur, left);
}
}
}
return (function () {
// 从(n/2-1) --> 0逐次遍历。遍历之后,得到的数组实际上是一个二叉堆
for (var len = arr.length, i = Math.floor(len / 2) - 1; i >= 0; i--) {
heap(arr, i, len - 1, order == 'asc');
}
// 从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素
for (i = len - 1; i > 0; i--) {
changePos(keys, 0, i);
changePos(arr, 0, i);
// 调整arr[0...i - 1],使得arr[0...i - 1]仍然是一个最大/小堆
// 即,保证arr[i - 1]是arr[0...i - 1]中的最大/小值
heap(arr, 0, i - 1, order == 'asc');
}
})();
}
// 测试
var aa = [5, 2, 8, 9, 1, 3, 4, 7, 6];
var kk = arrayKeys(aa); // 原索引数组
heapSort(aa, kk, 'asc');
console.log(aa); // 排序后:[1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(kk); // 原索引:[4, 1, 5, 6, 0, 8, 7, 2, 3]
当然,也可以在确保安全的前提下把该方法写入Array.prototype.heapSort,这样就可以用数组直接调用了,代码略微修改一下即可,如下:
Array.prototype.heapSort = function (keys, order) {
var keys = ({}).toString.call(keys) == '[object Array]' ? keys : [],
order = (order + '').toLowerCase() == 'desc' ? order : 'asc';
// 交换位置
function changePos(arr, cur, left) {
var tmp;
tmp = arr[cur];
arr[cur] = arr[left];
arr[left] = tmp;
}
// 构造二叉堆
function heap(arr, start, end, isMax) {
var isMax = isMax == undefined ? true : isMax, // 是否最大堆,否为最小堆
cur = start, // 当前节点的位置
left = 2 * cur + 1; // 左孩子的位置
for (; left <= end; cur = left, left = 2 * left + 1) {
// left是左孩子,left + 1是右孩子
if (left < end && ((isMax && arr[left] < arr[left + 1]) || (!isMax && arr[left] > arr[left + 1]))) {
left++; // 左右子节点中取较大/小者
}
if ((isMax && arr[cur] >= arr[left]) || (!isMax && arr[cur] <= arr[left])) {
break;
} else {
// 原index跟随排序同步进行
changePos(keys, cur, left);
changePos(arr, cur, left);
}
}
}
return (function (arr) {
// 从(n/2-1) --> 0逐次遍历。遍历之后,得到的数组实际上是一个二叉堆
for (var len = arr.length, i = Math.floor(len / 2) - 1; i >= 0; i--) {
heap(arr, i, len - 1, order == 'asc');
}
// 从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素
for (i = len - 1; i > 0; i--) {
changePos(keys, 0, i);
changePos(arr, 0, i);
// 调整arr[0...i - 1],使得arr[0...i - 1]仍然是一个最大/小堆
// 即,保证arr[i - 1]是arr[0...i - 1]中的最大/小值
heap(arr, 0, i - 1, order == 'asc');
}
})(this);
};
经过测试发现,在数组没有相同元素的情况下,原索引的序列同排序后的数组是匹配的,但是在有相同元素的情况下,序列就比较乱,这是因为堆排序本来就不是一个稳定的排序,排序后所有元素的位置会被打乱。比如:数组[5, 5, 5, 5, 5],因为元素都一样,所以排序过程中应该是只做了比较,却并没有交换原来位置的,但实际得到的索引却是:[1, 2, 3, 4, 0],这也很好理解,因为堆排序假如是最大堆的话,根节点是要被交换到末尾的,而很明显元素首位的5即是根节点,所以得到了前面那个颠倒的序列。
这样一来就跟初衷有点不符了,所以我又重新模拟了一个稳定排序的算法----归并排序,效果已经达到了预期。
代码如下:
// 归并排序(过程:从下向上)
function mergeSort(arr, key, order) {
if (!isArray(arr)) return [];
var key = isArray(key) ? key : [];
// 对数组arr做若干次合并:数组arr的总长度为len,将它分为若干个长度为gap的子数组;
// 将"每2个相邻的子数组" 进行合并排序。
// len = 数组的长度,gap = 子数组的长度
function mergeGroups(arr, len, gap) {
// 对arr[0..len)做一趟归并排序
// 将"每2个相邻的子数组"进行合并排序
for (var i = 0; i + 2 * gap - 1 < len; i += gap * 2) {
merge(arr, i, i + gap - 1, i + 2 * gap - 1); // 归并长度为len的两个相邻子数组
}
// 注意:
// 若i ≤ len - 1且i + gap - 1 ≥ len - 1时,则剩余一个子数组轮空,无须归并
// 若i + gap - 1 < len - 1,则剩余一个子数组没有配对
// 将该子数组合并到已排序的数组中
if (i + gap - 1 < len - 1) { // 尚有两个子文件,其中后一个长度小于len - 1
merge(arr, i, i + gap - 1, len - 1); // 归并最后两个子数组
}
}
// 核心排序过程
function merge(arr, start, mid, end) {
var i = start; // 第1个有序区的索引,遍历区间是:arr数组中的[start..mid]
var j = mid + 1; // 第2个有序区的索引,遍历区间是:arr数组中的[mid + 1..end]
var aTmp = []; // 汇总2个有序区临时数组
var kTmp = [];
var isAsc = (order + '').toLowerCase() !== 'desc';
/* 排序过程开始 */
while (i <= mid && j <= end) { // 遍历2个有序区,当该while循环终止时,2个有序区必然有1个已经遍历并排序完毕
if ((!isAsc && arr[i] <= arr[j]) || (isAsc && arr[i] >= arr[j])) { // 并逐个从2个有序区分别取1个数进行比较,将较小的数存到临时数组aTmp中
aTmp.push(arr[i]);
kTmp.push(key[i++]);
} else {
aTmp.push(arr[j]);
kTmp.push(key[j++]);
}
}
// 将剩余有序区的剩余元素添加到临时数组aTmp中
while (i <= mid) {
aTmp.push(arr[i]);
kTmp.push(key[i++]);
}
while (j <= end) {
aTmp.push(arr[j]);
kTmp.push(key[j++]);
}
/*排序过程结束*/
var len = aTmp.length, k;
// 此时,aTmp数组是经过排序后的有序数列,然后将其重新整合到数组arr中
for (k = 0; k < len; k++) {
arr[start + k] = aTmp[k];
key[start + k] = kTmp[k];
}
}
// 归并排序(从下往上)
return (function (arr) {
// 采用自底向上的方法,对arr[0..len)进行二路归并排序
var len = arr.length;
if (len <= 0) return arr;
for (var i = 1; i < len; i *= 2) { // 共log2(len - 1)趟归并
mergeGroups(arr, len, i); // 有序段长度 ≥ len时终止
}
})(arr);
} // 数组原型链方法
Array.prototype.mergeSort = function (key, order) {
var key = ({}).toString.call(key) == '[object Array]' ? key : [];
function mergeGroups(arr, len, gap) {
for (var i = 0; i + 2 * gap - 1 < len; i += gap * 2) {
merge(arr, i, i + gap - 1, i + 2 * gap - 1);
}
if (i + gap - 1 < len - 1) {
merge(arr, i, i + gap - 1, len - 1);
}
}
// 核心排序过程
function merge(arr, start, mid, end) {
var i = start;
var j = mid + 1;
var aTmp = [];
var kTmp = [];
var isAsc = (order + '').toLowerCase() !== 'desc';
/* 排序过程开始 */
while (i <= mid && j <= end) {
if ((isAsc && arr[i] <= arr[j]) || (!isAsc && arr[i] >= arr[j])) {
aTmp.push(arr[i]);
kTmp.push(key[i++]);
} else {
aTmp.push(arr[j]);
kTmp.push(key[j++]);
}
}
while (i <= mid) {
aTmp.push(arr[i]);
kTmp.push(key[i++]);
}
while (j <= end) {
aTmp.push(arr[j]);
kTmp.push(key[j++]);
}
/*排序过程结束*/
var len = aTmp.length, k;
for (k = 0; k < len; k++) {
arr[start + k] = aTmp[k];
key[start + k] = kTmp[k];
}
}
// 归并排序(从下往上)
return (function (arr) {
var len = arr.length;
if (len <= 0) return arr;
for (var i = 1; i < len; i *= 2) {
mergeGroups(arr, len, i);
}
return arr;
})(this);
}; // 测试
var arr1 = [2, 2, 2, 2, 2, 2, 2, 2];
var arr2 = [5, 2, 7, 1, 2, 6, 6, 8];
var key1 = arrayKeys(arr1);
var key2 = arrayKeys(arr2);
console.log(arr1.mergeSort(key1)); // [2, 2, 2, 2, 2, 2, 2, 2]
console.log(key1); // [0, 1, 2, 3, 4, 5, 6, 7]
console.log(arr2.mergeSort(key2)); // [1, 2, 2, 5, 6, 6, 7, 8]
console.log(key2); // [3, 1, 4, 0, 5, 6, 2, 7]
Javascript 数组自定义排序,并获取排序后的保存原索引的同序数组(堆排序实现)的更多相关文章
- javascript 设置input框只读属性 获取disabled后的值并传给后台
input只读属性 有两种方式可以实现input的只读效果:disabled 和 readonly. 自然两种出来的效果都是只能读取不能编辑,可是两者有很大不同. Disabled说明该input ...
- JavaScript数组对象常用方法
JavaScript数组对象常用方法 方法 形式 返回值 是否改变原数组 描述 concat -items: ConcatArray[] 追加之后的数组 否 连接两个或更多的数组,并返回结果.注意 c ...
- 对JavaScript对象数组按指定属性和排序方向进行排序
引子 在以数据为中心的信息系统中,以表格形式展示数据是在常见不过的方式了.对数据进行排序是必不可少的功能.排序可以分为按单个字段排序和按多个字段不同排序方向排序.单字段排序局限性较大,不能满足用户对数 ...
- Javascript数组(一)排序
一.简介首先,我们来看一下JS中sort()和reverse()这两个函数的函数吧reverse();这个函数是用来进行倒序,这个没有什么可说的,所谓倒序就是大的在前面,小的在后面. 比如: var ...
- javascript数组对象排序
javascript数组对象排序 JavaScript数组内置排序函数 javascript内置的sort函数是多种排序算法的集合 JavaScript实现多维数组.对象数组排序,其实用的就是原生的s ...
- 19 JavaScript数组 &数组增删&最值&排序&迭代
关联数组(散列) 关联数组又叫做散列,即使用命名索引. JavaScript数组只支持数字索引. JavaScript对象使用命名索引,而数组使用数字索引,JavaScript数组是特殊类型的对象. ...
- Qt之QHeaderView自定义排序(获取正确的QModelIndex)
简述 前几节中分享过关于自定义排序的功能,貌似我们之前的内容已经可以很好地解决排序问题了,但是,会由此引发一些很难发现的问题...比如:获取QModelIndex索引错误. 下面,我们先来实现一个整行 ...
- php中数组自定义排序
php中数组自定义排序方法有很多,现在只提usort();不会保留原有键名. unsort调用方法就是unsrot($arr,func); 注意: 如果func是写在当前类中的话,那么调用的方式是 u ...
- 数组自定义排序:IComparable和IComparer接口
首先先说一下IComparable和IComparer的区别,前者必须在实体类中实现,后者可以单独出现在一个排序类中,即此类只包含一个compare方法. Array类使用快速算法对数组中的元素进行排 ...
随机推荐
- 【CSS】Beginner3:Color
1.red rgb(255,0,0) rgb(100%,0%,0%) #ff0000 #f00 2.Predefined color name aqua, black, blue, fuchsia, ...
- HDU 4622 Reincarnation(SAM)
Problem Description Now you are back,and have a task to do:Given you a string s consist of lower-cas ...
- spoj 1812 LCS2(SAM+DP)
[题目链接] http://www.spoj.com/problems/LCS2/en/ [题意] 求若干个串的最长公共子串. [思路] SAM+DP 先拿个串建个SAM,然后用后面的串匹配,每次将所 ...
- 关于easyui模拟win2012桌面的一个例子系列
最近时间比较充裕,想到之前领导问我,什么界面更适合公司这种屏幕小但是又要求可以同时处理更多的工作. 我感觉 windows是最合适的,毕竟微软已经做了这么多年的系统了,人的操作习惯已经被他们确定了. ...
- springMVC学习笔记--初识springMVC
前一段时间由于项目的需要,接触了springMVC(这里主要是讲3.1版,以下内容也是围绕这个版本展开),发觉其MVC模式真的很强大,也简单易用,完全是基于注解实现其优雅的路径配置的.想想以前接手的项 ...
- ECharts中文显示为Unicode码
后台遍历出的数据,在ECharts的js中引用为Unicode码 解决方案: <s:property>标签的escape属性默认值为true,即不解析html代码,直接将其输出. 若想要输 ...
- POJ 3621Sightseeing Cows
Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 9851 Accepted: 3375 Description Farme ...
- Oracle学习过程(随时更新)
1.入门 实用的一些查询语句: 查询用户所有表注释 select * from user_tab_comments 条件查询 根据两个值查询 select*from table where 字段 in ...
- 编译Android4.3内核源代码
--------------------------------------------------------------------------------------------------- ...
- jfinal获取当前访问路径和端口号
public void generateSingleLicense() throws Exception { System.out.println(getRequest().getRequestURL ...