title: 数据结构与算法-排序一
date: 2020-06-26 14:30:45
tags:
- 数据结构与算法
- 比较排序
算法的效率表示。
- 常数级 o(1)
- 对数级 o(log(n))
- 线性级 o(n)
- 线性对数级 o(nlog(n))
- 平方级 o(n^2)
- 指数级 o(2^2)
简单排序(都以升序为例)
冒泡排序,选择排序
-
1,冒泡排序法
-
两层循环
-
比较次数为(n-1)+(n-2)+…+2+1=n(n-1)/2,所以冒泡排序法的时间复杂度为o(n^2)。
-
不是每次比较都要交换,交换的概率为1/2,所以交换次数为n(n-1)/4;
-
-
2,选择排序法
- 两层循环
- 比较次数为(n-1)+(n-2)+…+2+1=n(n-1)/2,所以选择排序法的时间复杂度为o(n^2)。
- 经过一层循环后挑出最值才进行交换,只交换n-1次;所以交换次数减少为o(n)
-
3,插入排序
- 局部有序的思想!有序部分越来越长。
- 因为不需要全部比较,只需要和有序部分进行比较(向前比较),所以可能的比较次数减少了。不过如果是以数组形式的来表示的话,插入,移动元素时可能会浪费一些资源。
- 比较次数减少一半为n(n-1)/4,移动次数也为n(n-1)/4;移动(复制)没有比较消耗性能!比较操作最耗费性能。插入排序的时间复杂度为o(n^2)。
不同时间复杂度的排序,首先看时间复杂度;相同时间复杂度情况下,看比较次数。
效率,插入排序>选择排序>冒泡排序;所有简单排序都是稳定的。
高级排序
-
1,希尔排序
- 插入排序,新插入数据总是以步长1向前寻找插入位置,如果新插入数据比较小,那么需要移动的中间数据量就比较大。希尔排序可以做到不必移动所有中间数据。
- 不是一次性把数据移动到正确位置;但是每次移动都会离正确位置更近!
- 一开始间隔较大,最后间隔为1。常见增量序列为半数增量(每次长度缩小一倍);奇数序列增量1,3,5,7,…。
- 同一gap的分组进行插入排序,gap逐渐缩减到1。所以有**三层循环!**gap控制一层;相同gap不同分组的插入排序两层!
- 希尔的效率因为难以证明,所以简单的认为效率为o(n^1.5)
-
2,归并排序
- 将原数组不断拆分到长度为1;可以用递归实现
- 不断将已经排序的数据合并到原数组。空间复杂度比较不理想,产生了许多小的零时数组。
- 初步理解分治策略,就是把大问题分解到小问题,再由小问题合并成大问题,有个分解的过程,这里面的**小问题可以用其他的方法解决。**小问题到大问题的合成中涉及到的问题就是合并有序的数组。
- 先拆后排序合并,o(nlog(n))。
-
3,快速排序
- 适用性最广,几乎最快。
- 一次循环就可以找到某个元素的正确位置,那么这个枢纽的选取? 取头中尾的中位数。
- 分而治之的思想,每次选的那个元素,就可以找到它正确的位置,左小右大,那么它的位置就确定了。每次选一个元素,最终递归完成,排序完成。
- 在拆分的过程同时排序,o(nlog(n))。
//先封装一个列表封装数据
function ArrayList() {
//属性
this.array = []
//插入,展示,交换
ArrayList.prototype.insert = function (item) {
this.array.push(item)
}
ArrayList.prototype.toString = function () {
return this.array.join('-')
}
ArrayList.prototype.swap = function (a, b) {
let temp = this.array[a]
this.array[a] = this.array[b]
this.array[b] = temp
}
//排序算法,升序
//简单排序
//1,冒泡排序
ArrayList.prototype.bubblesort = function () {
let length = this.array.length
for (let i = 0; i < length - 1; i++) {
for (let j = i; j < length; j++) {
if (this.array[j - 1] > this.array[j]) {
this.swap(j - 1, j)
}
}
}
return this//返回对象,以方便继续调用对象方法
}
//2,选择排序
ArrayList.prototype.selectionsort = function () {
let length = this.array.length
for (let i = 0; i < length - 1; i++) {
let min = i//初始最小值下标为i
for (let j = i; j < length; j++) {
if (this.array[min] > this.array[j]) {
min = j//循环一趟造成最小值下标j
}
}
this.swap(i, min)//真实最小值下标,找到才交换,所以交换次数减少
}
return this
}
//3,插入排序
ArrayList.prototype.insertionsort = function () {
let length = this.array.length
for (let i = 1; i < length; i++) {//从第二个位置获取数据,向前面有序部分进行插入
//内层循环,获取位置i数据,和前面的数据进行比较
let temp = this.array[i]
let j = i
while (this.array[j - 1] > temp && j > 0) {//不确定循环次数,向前比较,寻找插入位置。与有序部分比较减少次数。
this.array[j] = this.array[j - 1]//元素后移
j--//继续往前找
}
//把temp插入到正确位置
this.array[j] = temp
}
return this
}
//高级排序
//1,希尔排序
ArrayList.prototype.shellsort = function () {
let length = this.array.length
//初始化增量
let gap = Math.floor(length / 2)
//第一层while循环减小gap
while (gap >= 1) {
//第二层循环同一gap不同分组的创建
//i等于gap,就是从每组的第二个元素开始
for (i = gap; i < length; i++) {//这里是i++,因为同一gap下的不同分组的起始值是连续分布的,增量为1。
temp = this.array[i]
let j = i
//第三层循环同一gap同一分组的元素进行插入排序
while (this.array[j - gap] > temp && j > 0) {//同一gap的同一分组的不同元素增量是gap
this.array[j] = this.array[j - gap]
j -= gap
}
this.array[j] = temp
}
gap = Math.floor(gap / 2)
}
return this
}
//2,归并排序
ArrayList.prototype.mergesort = function () {
return this.split(0, this.array.length)
}
//拆分,递归实现,拆分不时间产生零时数组,只是计算好位置
ArrayList.prototype.split = function (left, right) {
//1,终止条件,直到数组长度为一时返回
if (right - left < 2) return this
let center = Math.floor((left + right) / 2)
this.split(left, center)//左边
this.split(center, right)右边
return this.merge(left, center, right)//利用计算好的位置调用合并,同层次的合并,在最后一步返回就好了
}
//合并
ArrayList.prototype.merge = function (left, center, right) {
//定义两个零时空间
let arr1 = this.array.slice(left, center)//注意是不包括结束位置的是开区间
let arr2 = this.array.slice(center, right)
//追加两个正无穷,这样可以减少边界值判断的复杂程度
arr1.push(Number.MAX_SAFE_INTEGER)//用i来记录
arr2.push(Number.MAX_SAFE_INTEGER)//用j来记录
//i和j为两个零时数组的指针变量,k为被写入数组的位置变量
for (let i = 0, j = 0, k = left; k < right; k++) {//因为有正无穷所以免去了ij的判断
// if(arr1[i]<arr2[j]){
// this.array[k]=arr1[i]
// i++
// }else{
// this.array[k]=arr2[j]
// j++
// }
this.array[k] = arr1[i] < arr2[j] ? arr1[i++] : arr2[j++]//合并判断赋值和变量累加
}
return this
}
//3,快速排序
// 选择枢纽
ArrayList.prototype.pivot = function (left, right) {
// 1.求出中间的位置
var center = Math.floor((left + right) / 2)
// 2.判断并且进行交换
if (this.array[left] > this.array[center]) {
this.swap(left, center)//二者大的到了中间
}
if (this.array[center] > this.array[right]) {
this.swap(center, right)//最大值到了右边
}
if (this.array[left] > this.array[center]) {
this.swap(left, center)//次大值到了中间,注意又是比较left与center!!!
}
// 3.将center移动到right - 1的位置;这样比较操作可以减少一个
this.swap(center, right - 1)
// 4.返回pivot
return this.array[right - 1]//后面要以这个值为基准进行比较。
}
ArrayList.prototype.quicksort = function () {
return this.quicksortRec(0, this.array.length - 1)//初始化调用递归并返回最终对象
}
ArrayList.prototype.quicksortRec = function (left, right) {
//1,递归结束条件
if (left >= right) return this//返回设置好的对象
//2,递归函数体
//获取枢纽
let pivot = this.pivot(left, right)
//指针记录变量,这里的记录的初始值的设定决定了递归调用区间的开闭性。
let i = left + 1//left已经比right-1枢纽小
let j = right - 2//right已经比pivot比较,而且没必要和自己比较
while (true) {//i<=j时,继续进入循环,找下一对可以交换的。
while (this.array[i] < pivot) {//在左边找到比枢纽大的值停下,这里也是要循环的,如果只有if,只变一次
i++
}
while (this.array[j] > pivot) {//在右边找到比枢纽小的值停下
j--
}
if (i < j) {//等于就没必要交换,自己和自己交换没意义
this.swap(i, j)
} else {
break//不写这句,就算不交换也不会跳出循环
}
}
//3,递归调用
//这里时i-1与i取决于i和j的初始值的设置;如果设置是j=right-1,那么右区间的就是从i+开始
this.quicksortRec(left, i-1)//左边设置完毕
return this.quicksortRec(i, right)//右边也设置完毕,返回该对象
}
}
let list = new ArrayList()
list.insert(66)
list.insert(88)
list.insert(12)
list.insert(87)
list.insert(100)
list.insert(5)
list.insert(566)
list.insert(23)
list.insert(99)
//所有方法均返回设置好的对象,以便于统一调用toString方法以供展示
console.log(list.toString())//66-88-12-87-100-566-23
//console.log(list.bubblesort().toString())//冒泡排序12-23-66-87-88-99-100-566
//console.log(list.selectionsort().toString())//选择排序
//console.log(list.insertionsort().toString())//插入排序
//console.log(list.shellsort().toString())//希尔排序
//console.log(list.mergesort().toString())//归并排序
console.log(list.quicksort().toString())//快速排序