• 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]

      转载自:https://www.cnblogs.com/go-jzg/p/5812021.html

  • 相关阅读:
    JAVA-throw new IOException报错unhandled exception:java.lang.Exception 2021年6月7日
    GIt保持远程 源仓库与Fork仓库同步--2017年6月13日
    Python的getattr()-2017年6月7日
    JavaScript学习-2017年5月18日
    Writing your first Django app--2017年5月9日
    M4-AC6 Oh,Trojan Again--2017年5月9日
    吴军硅谷来信
    【1】Prologue--A Game of Thrones--2017年4月8日
    M4-PC9 Read 10,000 Books,Travel 10,000 Miles--2017年5月8日
    资源分配图RAG的化简
  • 原文地址:https://www.cnblogs.com/emojio/p/12408193.html
Copyright © 2020-2023  润新知