排序算法

选择排序

  • 每次循环从待排序的数据中选出最小值,放在已排序数据的末尾
for (i = 0; i < arr.length; i++) {
    // 假设arr[i]为最小值
    var minIndex = i
    for (j = i+1; j < arr.length; j++) {
        if (arr[minIndex] > arr[j]) {
            // 找到更小的值,更新minIndex
            minIndex = j
        }
    }
    // 最小值和arr[i]交换位置
    var temp = arr[i]
    arr[i] = arr[minIndex]
    arr[minIndex] = temp
}

冒泡排序

  • 相邻元素之间比较
  • 每次内循环一定是把最大的元素放到最后(假设a是最大的元素,其他元素(都比他小)与他比较之后都会进行交换,a一直往右挪)
  • 所以结束条件可以设置为全部最大元素都排好序了
// 外循环  j可以看作已排好序的元素个数
for (j = 0; j < arr.length - 1; j++) {
    // 内循环 把最大的元素放到最后
    for (i = 0; i < arr.length - 1 - j; i++) {
        if (arr[i] > arr[i+1]) {
            // 交换
            temp = arr[i]
            arr[i] = arr[i+1]
            arr[i+1] = temp
        }
    }
}

插入排序

  • 从第一个元素开始排序(因为默认第0个元素是有序的)
  • 第n个元素会和第n-k(1 <= k <= n)个元素比较,直到找到了合适的位置
  • 非常适合用于对几乎有序的数据排序
  • 和希尔排序的思想是一样的
for (i = 1; i < arr.length; i++) {
    // key与前面的数据比较
    key = arr[i]
    for (j = i-1; j >= 0; j--) {
        if (arr[j] > key) {
            // 交换位置
            arr[j+1] = arr[j]
            arr[j] = key
        }
        // 前面(有序的)没有比key大的值了,找到了合适的位置
        else
            break
    }
}

/********************************
**优化  找到最合适的位置才进行交换
*********************************/

for (i = 1; i < arr.length; i++) {
    // key与前面的数据比较
    var key = arr[i]
    var j
    for (j = i-1; j >= 0; j--) {
        if (arr[j] > key) {
            // 比key大的值往后挪一位
            arr[j+1] = arr[j]
        }
        else
            break 
    }
    arr[j+1] = key
}

希尔排序

归并排序

  • 将排好序的两个子数组归并成一个有序数组

自顶向下

  • 一直将数组划分为一半(得到两个子数组)直到子数组元素个数为1
  • 归并:两个子数组元素比较排序,得到一个有序数组
// 分
function Sort(arr, left, right) {
    if (left >= right){
        // 只有一个元素了
        return
    }

    // (left + right) 可能溢出
    // parseInt(left + (right-left) / 2)
    var mid = parseInt((left + right) / 2)
    // 左边
    Sort(arr, left, mid)
    // 右边
    Sort(arr, mid + 1, right)
    // 自顶向下到了底层
    // 如果左边最后一个元素 < = 右边第一个元素  不用merge
    if (arr[mid] > arr[mid+1]) {
        Merge(arr, left, mid, mid + 1, right)
    }
}

function Merge (arr, leftStart, leftEnd, rightStart, rightEnd) {
    // 全部复制
    // var temp = arr.concat([])

    // 只复制需要的部分 不能用splice
    var i = 0
    var j = leftStart
    var temp = []
    while (j <= rightEnd) {
        temp[i++] = arr[j++]
    }

    // temp的leftStart
    i = 0

    // temp的leftEnd
    var iend = leftEnd - leftStart

    // temp的rightStart
    j = rightStart - leftStart

    // temp的rightEnd
    var jend = rightEnd - leftStart

    while (i <= iend && j <= jend) {
        if (temp[i] < temp[j]) {
            arr[leftStart++] = temp[i++]
        } else {
            arr[leftStart++] = temp[j++]
        }
    }
    /************不写下面代码也行************ 
    *** 修改 while (i <= iend || j <= jend)
    *** 判断的时候 数据undefined  false  走else分支  也能将剩下部分复制
    ***************************************/

/***********可忽略部分开始*************/
    // 越界 将剩下部分全部复制
    if(i > iend) {
        while (j <= jend) {
            arr[leftStart++] = temp[j++]
        }
    } else {
        while (i <= iend) {
            arr[leftStart++] = temp[i++]
        }
    }
/***********可忽略部分结束*************/
}

var array = [21,32,43,98,54,45,23,4,66,86,30]
Sort(array, 0, array.length-1)
console.log(array)

自底向上(链表更适合)

for (i = 1; i < arr.length; i += i) {
    // i 表示子数组元素个数
    // j 表示子数组开始位置
    for (j = 0; j + i < arr.length; j += i + i) {
        // rightStart < arr.length  右边存在
        // 如果右边不存在 说明不用merge  且左边已经排好序了
        // Merge
        Merge(arr, j, j + i -1, j + i, Math.min(arr.length - 1, j + i + i - 1))
        // leftStart = j
        // leftEnd = j + i -1
        // rightStart = j + i
        // rightEnd = j + 2*i - 1
        // rightEnd可能越界  min(arr.length - 1, rightEnd)
        // Merge(arr, j, j + i -1, j + i, j + i + i - 1)  
    }
}

// 自底向上举个例子                        i        j--------->
// 1 1 1 1 1 1 1 1 1 1 1 1               1        0     2    4
// 11  11  11  11 11  11                 2        0     4    8
// 1111   1111   1111                    4        0     8    16
// 11111111      1111                    8        0     16   32

快速排序

  • 设数组的第一个元素为key
  • 将数组分为大于key和小于key的部分
  • 设定两个指针:left 指向数组第0个元素,right指向最后一个元素
  • 从后向前,找小于key的值,找到,交换arr[left]和arr[right],left++
  • 从前往后,找大于key的值,找到,交换arr[left]和arr[right],right--,继续从后向前找
  • 当 left >= right,已将数组分为大于key和小于key的部分
  • 每个部分又进行快排
function Quick (arr, left, right) {
    var originLeft = left
    var originRight = right
    var key = arr[left]
    while (left < right) {
        // 从后往前 找小于key的值  找到就结束
        for (; left < right; right--) {
            // 找到  交换  小的挪前面  left++  结束
            if (arr[right] < key) {
                arr[left++] = arr[right]
                arr[right] = key
                break
            }
        }

        // 从前往后 找大于key的值  找到就结束
        for (; left < right; left++) {
            // 找到  交换  大的挪后面  right-- 结束
            if (arr[left] > key) {
                arr[right--] = arr[left]
                arr[left] = key
                break
            }
        }
    }

    // left >= right  已经分成两部分了
    // [originLeft, left - 1] <= key <= [left + 1, originRight]

    // [originLeft, left - 1]  区间元素个数 > 1
    if (left - 1 > originLeft) {
        Quick(arr, originLeft, left - 1)
    }
    
    // [left + 1, originRight]   区间元素个数 > 1
    if (originRight > left + 1) {
        Quick(arr, left + 1, originRight)
    }
}

var arr = [21,15,32,43,98,54,45,23,4,66,86]
Quick (arr, 0, arr.length-1)
console.log(arr)
快排缺点
  • 在几乎有序的数据排序,效率不好,因为查找树不平衡
  • 解决:随机决定key,不选arr[left]为key
/******随机指定key*************
*******减少交换次数************/
function Quick (arr, left, right) {
    var originLeft = left
    var originRight = right

    // 得到在闭区间[left, right] 的随机数
    var random = parseInt(Math.random() * (right - left + 1) + left)

    // 随机指定key
    var key = arr[random]

    // 交换arr[random] 和 arr[left] 
    arr[random] = arr[left]
    arr[left] = key
 
    while (left < right) {
        while (left < right) {
            if (arr[right] < key) {
                // 先不交换
                break
            }
            right--
        }

        while (left < right) {
            if (arr[left] > key) {
                // 交换
                var temp = arr[left]
                arr[left] = arr[right]
                arr[right--] = temp
                break
                // 交换后不能left++  left一定要指向小于等于key部分的最后一个元素
                // 因为最后会用left的位置保存key
                // arr[oringLeft] = arr[left]
            }
            left++
        }
    }

    // left == right  将小于等于key部分的最后一个元素 和 key 的交换
    arr[originLeft] = arr[left]
    arr[left] = key

    // 已经分成两部分了
    // [originLeft,left - 1] <= key <= [left + 1, originRight]
    if (left - 1 > originLeft) {
        Quick(arr, originLeft, left - 1)
    }

    if (originRight > left + 1) {
        Quick(arr, left + 1, originRight)
    }
}

var array = [21,32,78,43,98,54,23,4,56,43,45,6,86,30]
Quick(array, 0, array.length-1)
console.log(array)

堆排序

  • 主要用于动态数组的排序,优先级操作

二叉堆

  • 树状结构,每个父节点最多只有2个子节点
  • 一定从左到右填满整层

最大堆

  • 父节点一定大于子节点的二叉堆
堆索引从1开始,如下图(图片来自网络)
  • 假设 i 是parent 的索引
    leftChild 的索引:2 * i
    rightChild 的索引:2 * i + 1

  • 假设 i 是child 的索引
    parent 的索引:parseInt( i / 2 )


    heap1.jpg
堆索引从0开始,如下图(图片来自网络)
  • 假设 i 是parent 的索引
    leftChild 的索引:2 * i + 1
    rightChild 的索引:2 * i + 2

  • 假设 i 是child 的索引
    parent的索引:parseInt(( i - 1 ) / 2)


    heap0.jpg
构建最大堆,堆索引从1开始
  • 新插入的元素和父节点比较,小于,直接插入,大于,父节点变为子节点,再和父节点的父节点比较(shiftUp)...
  • 父节点可能没有父节点了
  • 可以不开辟新空间,直接对传入的数据进行操作
function MaxHeap (arr) {
    var heap = []
    // 孩子节点
    var childIndex
    // 父节点
    var parentIndex 

    // 堆的索引从1开始 
    // index指向数据要插入的位置
    var index = 1

    // 遍历arr
    var i = 0
    while (i < arr.length) {
        childIndex = index
        parentIndex = parseInt(childIndex / 2)
        // 插入值 <= 父节点
        // heap[0]  undefined
        if (arr[i] <= heap[parentIndex] || heap[parentIndex] === undefined) {
            // 直接插入
            heap[index++] = arr[i++]
        } else {
            // 父节点 < 插入值   shift up
            // 新增节点的值 = 父节点的值
            heap[index++] = heap[parentIndex]
            // 新孩子节点 指向  当前父节点
            // 新父节点   指向  新孩子节点的父节点
            childIndex = parentIndex
            parentIndex = parseInt(childIndex / 2)

            // heap[childIndex]可以保存arr[i]?看父节点值  
            while (heap[parentIndex] < arr[i]) {
                // 父节点 < 插入值
                heap[childIndex] = heap[parentIndex]
                // 更新 childIndex  parentIndex
                childIndex = parentIndex
                parentIndex = parseInt(childIndex / 2)
                // 可能 parentIndex === 0
                // heap[0] === undefined
                // undefined 和数字比较都是false
            }
            heap[childIndex] = arr[i++]
        }
    }
    console.log(heap)
    return heap
}

弹出最大值,保持最大堆性质
  • 输出第一个元素,令第一个元素 = 最后一个元素,删除最后一个元素
  • shiftDown:从根节点开始,如果孩子节点 > 父节点,父节点和大的孩子节点交换,直到符合最大堆
  • 可能没有右孩子
function DelMaxHeap(heap) {
    // 父节点
    var parent = 1
    // 取第一个元素 第0位是空的
    console.log('取出第一个元素:'+heap[parent])

    // 将最后一个元素放在根节点
    heap[parent] = heap[heap.length-1]

    // 删除最后一个元素
    heap.pop()

    // 左孩子
    var child = 2
    // 左右孩子比较
    // 存在 孩子比父节点大
    // 这个是有了两个孩子的情况 
    // while (child < heap.length-1 && Math.max(heap[child], heap[child+1]) > heap[parent]) {

    
    while (child < heap.length-1) {
        // 一定有左孩子  右孩子不一定有
        // 左孩子 > 父节点  || 右孩子 > 父节点
        if (heap[child] > heap[parent] || heap[child+1] > heap[parent]) {
            if (heap[child] < heap[child + 1]) {
                // 左孩子 < 右孩子
                // 父节点和右孩子交换
                let temp = heap[child + 1]
                heap[child + 1] = heap[parent]
                heap[parent] = temp
                // 更新parent 位置 = 右孩子的位置
                parent = child + 1
            } else {
                // 左孩子 >= 右孩子
                // 父节点和左孩子交换
                let temp = heap[child]
                heap[child] = heap[parent]
                heap[parent] = temp
                // 更新parent 位置 = 左孩子的位置
                parent = child
            }
            // 更新 child 位置
            child = 2 * parent
        } else {
            // 左右孩子 <= 父节点 
            // 左孩子 <= 父节点 && 右孩子不存在
            break
        }
    }
    console.log(heap)
}

/*************以下简化版***********
**********************************/
function DelMaxHeap(heap) {
    // 父节点
    var parent = 1
    // 取第一个元素 第0位是空的
    console.log('取出第一个元素:'+heap[parent])

    // 最后一个元素放根节点
    heap[parent] = heap[heap.length-1]

    // 删除最后一个元素
    heap.pop()

    // 左孩子
    var child = 2
    // 左右孩子比较
    // 存在 孩子比父节点大

    while (child < heap.length-1) {
        // 一定有左孩子  右孩子不一定有
        if (heap[child] < heap[child+1]) {
                // 左孩子 < 右孩子
                child = child+1
        }
        // 父节点和大的孩子交换
        if (heap[child] > heap[parent]) {
            let temp = heap[child]
            heap[child] = heap[parent]
            heap[parent] = temp
            // 更新parent 位置 = 左孩子的位置
            parent = child
            // 更新 child 位置
            child = 2 * parent
        } else {
            // 左右孩子 <= 父节点 
            // 左孩子 <= 父节点 && 右孩子不存在
            break
        }
    }
    console.log(heap)
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341