排序算法:
分类:
冒泡排序、选择排序、插入排序、归并排序、计数排序(counting sort)、基数排序(radix sort)、希尔排序、堆排序、桶排序.
简单排序: 冒泡排序 - 选择排序 - 插入排序
高级排序: 归并排序 - 快速排序
排序的简单算法的主要操作:
- 比较两个数据项.
- 交换两个数据项, 或者复制其中一项.
- 但是, 每种算法具体实现的细节有所不同.
创建列表:
创建一个列表封装我们的数据项
class ArrayList{
constructor(){
//创建列表
this.array=[]
}
// 向数组中添加数据
insert(ele){
this.array.push(ele)
}
// 将数组中的元素用字符串连接起来
tostring(){
return this.array.join()
}
}
简单排序:
1.冒泡排序:
定义: 冒泡排序算法相对其他排序运行效率较低, 但是在概念上它是排序算法中最简单的。
代码:
// 冒泡排序
bubbleSort(){
// 控制趟数 控制次数
for(let i=0;i<this.array.length-1;i++){
// console.log(i,"趟数")
for(let j=0;j<this.array.length-1;j++){
// 比较
if(this.array[j+1]<this.array[j]){
this.swap(j,j+1)
}
}
}
}
// 交换两个位置的值
swap(m,n){
// 交换两个位置的值
let temp = this.array[n]
this.array[n] = this.array[m]
this.array[m] = temp
}
冒泡排序的思路:
相邻元素进行比较,如果前一个小于后一个元素,就相互交换位置
- 对未排序的各元素从头到尾依次比较相邻的两个元素大小关系
- 如果左边的元素大, 则两元素交换位置
- 向右移动一个位置, 比较后面两个元素
- 当走到最右端时, 最大的元素一定被放在了最右边
- 按照这个思路, 从最左端重新开始, 这次走到倒数第二个位置的元素即可.
- 依次类推, 就可以将数据排序完成
图解:
每趟排序的过程如下:
2.选择排序:
定义:选择排序改进了冒泡排序, 将交换的次数由O(N²)减少到O(N), 但是比较的次数依然是O(N²)
代码:
selecttionSort(){
for(let i=0;i<this.array.length-1;i++){
let min=i;
for(let j=i+1;j<this.array.length;j++){
if(this.array[j]<this.array[min]){
min=j
}
this.swap(j,min)
}
}
}
选择排序的思路
在每一趟中找出最小的元素,放在第一个索引的位置
- 选定第一个索引位置,然后和后面元素依次比较
- 如果后面的元素, 小于第一个索引位置的元素, 则交换位置
- 经过一轮的比较后, 可以确定第一个位置是最小的
- 然后使用同样的方法把剩下的元素逐个比较即可
- 可以看出选择排序,第一轮会选出最小值,第二轮会选出第二小的值,直到最后
图解:
每趟排序的过程如下:
3.插入排序:
定义:插入排序是简单排序中效率最好的一种.
插入排序也是学习其他高级排序的基础, 比如希尔排序/快速排序, 所以也非常重要.
代码:
// 插入排序
insertionSort(){
for(let i=1;i<this.array.length;i++){
let j=i
let mark=this.array[i]
while(this.array[j-1]<mark&&j>0){
this.array[j]=this.array[j-1]
j--;
}
this.array[j]=mark
}
}
插入排序的思路:
从1开始,标记一个值,默认标记的值前面的元素都是已经排好序的,将标记的值开始逐渐与它的上一个元素相比较,如果小于上一个元素,就交换位置,并且继续与上一个元素相比较,直到前面已经没有元素可以比较;如果大于上一个元素,不交换位置,且停止与上一个元素比较
-
局部有序:
- 插入排序思想的核心是局部有序. 什么是局部有序呢?
- 比如在一个队列中的人, 我们选择其中一个作为标记的队员. 这个被标记的队员左边的所有队员已经是局部有序的.
- 这意味着, 有一部门人是按顺序排列好的. 有一部分还没有顺序.
-
插入排序的思路:
- 从第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素,在已经排序的元素序列中从后向前扫描
- 如果该元素(已排序)大于新元素,将该元素移到下一位置
- 重复上一个步骤,直到找到已排序的元素小于或者等于新元素的位置
- 将新元素插入到该位置后, 重复上面的步骤.
图解:
高级排序:
1.归并排序:
代码:
// 归并排序
mergerSort(){
this.array=this.divide(this.array)
}
// 将数组分开
divide(arr){
if(arr.length<=1){
return arr
}else{
let index=Math.floor(arr.length/2)
let leftarr=arr.slice(0,index)
let rightarr=arr.slice(index)
return this.merge(this.divide(leftarr),this.divide(rightarr))
}
}
// 将所有的数组合起来
merge(left,right){
let newarr=[]
while(left.length&&right.length){
if(left[0]<right[0]){
newarr.push(left.shift())
}else{
newarr(right.shift())
}
}
return newarr.concat(left,right)
}
基本思想与过程:
先递归的分解数列,再合并数列(分治思想的典型应用)
(1)将一个数组拆成A、B两个小组,两个小组继续拆,直到每个小组只有一个元素为止。
(2)按照拆分过程逐步合并小组,由于各小组初始只有一个元素,可以看做小组内部是有序的,合并小组可以被看做是合并两个有序数组的过程。
(3)对左右两个小数列重复第二步,直至各区间只有1个数。
下面对数组【42,20,17,13,28,14,23,15】进行归并排序,模拟排序过程如下:
第一步:拆分数组,一共需要拆分三次(logN);
第一次拆成【42,20,17,13】,【28,14,23,15】,
第二次拆成【42,20】,【17,13】,【28,14】,【23,15】,、
第三次拆成【42】,【20】,【17】,【13】,【28】,【14】,【23】,【15】;
第二步:逐步归并数组,采用合并两个有序数组的方法,每一步其算法复杂度基本接近于O(N)
第一次归并为【20,42】,【13,17】,【14,28】,【15,23】
第二次归并为【13,17,20,42】,【14,15,23,28】,
第三次归并为【13, 14, 15, 17, 20, 23, 28, 42】
图解:
2.快速排序:
快速排序几乎可以说是目前所有排序算法中, 最快的一种排序算法.
当然, 没有任何一种算法是在任意情况下都是最优的, 比如希尔排序确实在某些情况下可能好于快速排序. 但是大多数情况下, 快速排序还是比较好的选择.
快速排序是什么?
- 希尔排序相当于插入排序的升级版, 快速排序其实是我们学习过的最慢的冒泡排序的升级版.
- 我们知道冒泡排序需要经过很多次交换, 才能在一次循环中, 将最大值放在正确的位置.
- 而快速排序可以在一次循环中(其实是递归调用)找出某个元素的正确位置, 并且该元素之后不需要任何移动.
代码:
// 快速排序
quickSort(){
this.array=this.quick(this.array)
}
quick(arr){
if(arr.length<=1){
return arr
}else{
// 二分法
let index=Math.floor(arr.length/2);
let middle=arr.splice(index,1)[0]
let leftarr=[]
let rightarr=[]
for(let i=0;i<arr.length;i++){
if(arr[i]<middle){
leftarr.push(arr[i])
}else{
rightarr.push(arr[i])
}
}
// console.log(leftarr,rightarr)
return this.quick(leftarr).concat(middle,this.quick(rightarr))
}
}
快速排序的思想:
从数组中间取一个值,将数组中小于这个值的元素放在这个值左边的数组中,大于这个值的元素放在右边的数组中。再在这两个数组继续进行上面的操作,直到数组中只有一个元素为止停止操作。
- 快速排序最重要的思想是分而治之.
- 比如我们下面有这样一顿数字需要排序:
- 第一步: 从其中选出了65. (其实可以是选出任意的数字, 我们以65举个栗子)
- 第二步: 我们通过算法: 将所有小于65的数字放在65的左边, 将所有大于65的数字放在65的右边.
- 第三步: 递归的处理左边的数据.(比如你选择31来处理左侧), 递归的处理右边的数据.(比如选择75来处理右侧, 当然选择81可能更合适)
- 最终: 排序完成
图解:
查找算法(有两种):
// 1.搜索算法
shunxuSerach(ele){
for(let i=0;i<this.item.length;i++){
if(this.item[i]==ele){
return true
}
}
return false
}
// 2.二分法查找,必须有序
BinarySearch(ele){
let start=0;
let end=this.item.length-1;
if(ele<this.item[start]||ele>this.item[end]){
return flase
}
while(start<=end){
let middle=Math.floor((start+end)/2)
if(ele<this.item[middle]){
end=middle-1
}else if(ele>this.item[middle]){
start=middle+1
}else{
return true
}
}
return false
}