1、算法分类:(1)比较类排序[非线性](2)非比较类排序[线性]
2、(1.1)稳定:a=b,a在b前,排序后仍保持a在b前
(1.2)不稳定:排序后a可能会出现在b后
(2.1)时间复杂度:排序数据总操作次数
(2.2)空间复杂度:存储空间度量
排序算法
一、冒泡排序
平均时间复杂度:O() 稳定:相同大小元素,不进行交换位置
用代码表示则如下:
function bubbleSort(arr){
var len=arr.length;
for(let i=0;i<len-1;i++){
for(let j=0;j<len-1-i;j++){
if(arr[j]>arr[j+1]){
let temp=arr[j+1];
arr[j+1]=arr[j];
arr[j]=temp;
}
}
}
return arr;
}
优化:标志性变量exchange,如果某一趟都未进行交换数据,结束排序
function bubbleSort1(arr){
var i=arr.length-1;
while(i>0){
let pos=0;
for(let j=0;j<i;j++)
{
if(arr[j]>arr[j+1]){
let temp=arr[j+1];
arr[j+1]=arr[j];
arr[j]= temp;
pos=j; //记录最后交换的位置
}
}
i=pos; //判断是否需要下一趟
}
return arr;
}
二、选择排序
平均时间复杂度::O()不稳定:[原理]查找最小(大)元素,存放到数组起始位置,交换后元素不再变换位置
适用:数据规模越小越好
function selectionSort(arr){
var len=arr.length;
var minIndex,temp;
for(let i=0;i<len-1;i++){
minIndex=i;
for(let j=i+1;j<len;j++){
if(arr[j]<arr[minIndex]){
minIndex=j;
}
}
temp=arr[i];
arr[i]=arr[minIndex];
arr[minIndex]=temp;
}
return arr;
}
三、插入排序
平均时间复杂度:O()稳定:[原理]从左到右比较插入,建立有序数组,类似扑克牌排序;如果相同元素,则插入到该元素后面
适用:数据量不大,算法稳定性强,数据局部或整体有序排列
function insertionSort(arr){
var len=arr.length;
var preIndex,current;
for(var i=1;i<len;i++){
preIndex=i-1;
current=arr[i];
while(preIndex>=0&&arr[preIndex]>current){
arr[preIndex+1]=arr[preIndex];
preIndex--;
}
arr[preIndex+1]=current;
}
return arr;
}
四、希尔排序
平均时间复杂度:O()不稳定:[原理]间隔序列,简单插入排序改进版,优先比较远距离元素,又叫缩小增量排序。相同元素在各自插入排序中移动,可能会打乱稳定性。
function shellSort(arr){
var len=arr.length;
for(let gap=Math.floor(len/2);gap>0;gap=Math.floor(gap/2)){ //分组交替执行
for(var i=gap;gap<len;i++){
var j=i;
var current=arr[i];
while(j-gap>=0&¤t<arr[j-gap]){
arr[j]=arr[j-gap];
j=j-gap;
}
arr[j]=current;
}
}
return arr;
}
五、归并排序
平均时间复杂度:O()稳定:[原理]分治法,分-合两个步骤.,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也不会交换
适用:算法稳定,与选择算法类似性能不受输入数据影响,需要占据额外内存空间
function mergeSort(arr){
const len=arr.length;
if(len<2){
return arr;
}
var middle=Math.floor(arr/2);
var left=arr.slice(0,middle);
var right=arr.slice(middle);
return merge(mergeSort(left),mergeSort(right))
}
function merge(left,right){
var result=[];
while(left.length>0&&right.length>0){
if(left[0]<=right[0]){
result.push(left.shift());
}else{
result.push(right,shift());
}
}
while(left.length){
result.push(left.shift());
}
while(right.length){
result.push(right.shift());
}
return result;
}
六、快速排序
平均时间复杂度:O()不稳定:[原理]选取基准数字,头部(或尾部),如果元素的相等可能被分到不同的子数组中,改变它们的相对位置
适用:适宜内部排序,数据量过大且杂乱
function quickSort(arr){
const rec=(arr)=>{
if(arr.length<=1){return arr;}
const left=[];
const right=[];
const mid=arr[0];
for(let i=1;i<arr.length;i++){
if(arr[i]<mid){
left.push(arr[i]);
}else{
right.push(arr[i]);
}
}
return [...rec(left),...rec(right)];
}
return arr;
}
七、堆排序
平均时间复杂度:O()不稳定:堆积类似二叉树,将根节点和叶子节点交换后调整得到数组,可能会改变它们的相对位置
var len;
function buildMaxHeap(arr){ //建立大顶堆
len=arr.length;
for(var i=Math.floor(len/2);i>=0;i--){
heapify(arr,i);
}
}
function heapify(arr,i){ //调整
var left=2*i+1,
right=2*i+2,
largest=i;
if(left<len&&arr[left]>arr[largest]){
largest=left;
}
if(right<len&&arr[right]>arr[largest]){
largest=right;
}
if(largest!=i){
swap(arr,i,largest); //交换
heapify(arr,largest); //调整
}
}
function swap(arr,i,j){ //交换
var temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
function heapSort(arr){
buildMaxHeap(arr); //先建立一次大顶堆
for(var i=arr.length-1;i>0;i--){ //交换根节点和叶子节点元素
swap(arr,0,i);
len--;
heapify(arr,0); //调整
}
return arr;
}
八、计数排序
平均时间复杂度:O(n+k)稳定:[原理]不是比较排序算法,核心将输入数据值转化为键存储在开辟的额外数组空间。计数排序输入数据必须为有确定范围的整数 输入元素n个0到k的整数
适用:当k不是很大并且序列比较集中,排序速度快于任何比较排序算法
function countingSort(arr,maxValue){ //确定数组范围中最大值
var bucket=new Array(maxValue+1), //开辟额外数组空间
sortedIndex=0, //存放新数组元素个数
arrLen=arr.length,
buckLen=maxValue+1;
for(var i=0;i<arrLen;i++){ //把旧数组以键格式存放到新内存空间中
if(!buckLen[arr[i]]){
bucket[arr[i]]=0;
}
bucket[arr[i]]++;
}
for(var j=0;j<buckLen;j++){ //取出
while(buckLen[j]>0){
arr[sortedIndex++]=j;
buckLen[j]--;
}
}
return arr;
}
九、桶排序
平均时间复杂度:O(n+k)稳定:计数排序升级版,利用函数映射,将输入数据均匀分布到有限桶内
function bucketSort(arr,bucketSize){
if(arr.length===0){return arr;}
var i,
minValue=arr[0],
maxValue=arr[0];
for(i=1;i<arr.length;i++){
if(arr[i]<minValue){
minValue=arr[i];
}else if(arr[i]>maxValue){
maxValue=arr[i];
}
}
//桶的容量初始化
var DEFAULT_BUCKET_SIZE=10;
bucketSize=bucketSize||DEFAULT_BUCKET_EISE;
var bucketCount=Math.floor((maxValue-minValue)/bucketSize)+1;
var buckets=new Array(bucketCount);
for(i=0;i<buckets.length;i++){
buckets[i]=[];//初始化
}
//利用映射将数组元素存放入桶中
for(let i=0;i<arr.length;i++){
buckets[Math.floor((arr[i]-minValue)/bucketSize)].push(arr[i]);
}
//桶内排序并输出
arr.length=0;
for(i=0;i<buckets.length;i++){
insertionSort(buckets[i]); //使用插入排序对每个桶内的数据进行排序
}
for(var j=0;j<buckets[i].length;j++){
arr.push(buckets[i][j]);
}
}
return arr;
}
十、基数排序
平均时间复杂度:O(n*k)稳定:分别排序,分别收集,但性能比桶排序略差
var counter=[];
function radixSort(arr,maxDigit){
var mod=10; //求尾数,10内,100内
var dev=1; //整数,个位,百位
for(var i=0;i<maxDigit;i++,mod*=10,dev*=10){
for(var j=0;j<arr.length;j++){ //存入存储空间
var bucket=parseInt((arr[j]%mod)/dev);
if(counter[bucket]==null){
counter[bucket]=[];
}
counter[bucket].push(arr[j]);
}
var pos=0;//取出
for(var j=0;j<counter.length;j++){
var value=null;
if(counter[j]!=null){
while((value=counter[j].shift())!=null){ //取出判断是否有元素
arr[pos++]=value; //存入新数组中
}
}
}
}
return arr;
}
其他算法
一、LRU实现_最少使用置换算法
//使用构造函数
function LRU(length){
this.length=length;
this.arr=[];
}
//根据键值取数
LRU.prototype.get=function(key){
let index=this.arr.findIndex(item=>item.key=key);
if(index!==-1){ //如果存在
const result=this.arr.splice(index,1);
}
this.arr.push(result);
return result.val;
}
LRU.prototype.set=function(key,val){
let index=this.arr.findIndex(item=>item.key=key);
if(index!==-1){
this.arr.splice(index,1);
}
this.arr.push({key,value});
//如果超出限表删除最前列元素
if(this.length<this.arr.length){
this.arr.shift();
}
}
二、二叉树构建,广度/深度遍历
按照前序遍历出二叉树:
function createTree(nodeList){
if(Array.isArray(nodeList)){
const len=nodeList.length;
if(!len) return;
else{
//获取最前的节点数
const data=nodeList.shift();
let node=null; //构建二叉树
if(data){
node=new TreeNode(data);
node.left=createTree(nodeList);
node.right=createTree(nodeList);
}
return node;
}
}else{
throw Error("请输入一个节点序列");
}
}
const tree=createTree([1,3,null,null,5,7,9,null,11]);
console.log(tree);
(1)深度优先遍历
1.1递归实现(前中后序)
//二叉树前序遍历-DLR
function preOrderTraveral(nodeTree){
if(!nodeTree) return;
else{
console.log(nodeTree.data);
preOrderTraveral(nodeTree.left);
preOrderTraveral(nodeTree.right);
}
}
console.log("前序遍历:");
console.log(preOrderTraveral(tree));
//二叉树中序遍历-LDR
function middleOrderTraveral(nodeTree){
if(!nodeTree) return;
else{
middleOrderTraveral(nodeTree.left);
console.log(nodeTree.data);
middleOrderTraveral(nodeTree.right);
}
}
console.log("中序遍历:");
console.log(middleOrderTraveral(tree));
//二叉树后序遍历-LRD
function postOrderTraveral(nodeTree){
if(!nodeTree) return;
else{
postOrderTraveral(nodeTree.left);
postOrderTraveral(nodeTree.right);
console.log(nodeTree.data);
}
}
console.log("后序遍历:");
console.log(postOrderTraveral(tree));
1.2栈实现
/*栈实现深度优先遍历*/
//前序遍历
function stackPreTravel(nodeTree){
if(!nodeTree)return;
let stack=[];
let node=nodeTree;
//如果节点不为空,且栈不为空,则继续循环
while(node||stack.length){
//如果节点不为空,则继续向左边遍历
while(node){
console.log(node.data);
stack.push(node);
node=node.left;
}
//当左节点为空时,栈不为空,遍历右节点子树
while(stack.length){
node=stack.pop();
node=node.right;
}
}
}
//中序遍历
function stackMiddleTravel(nodeTree){
if(!nodeTree) return;
let stack=[];
let node=nodeTree;
while(node||stack.length){
while(node){
stack.push(node);
node=node.left;
}
while(stack.length){
node=stack.pop();
console.log(node.data);
node=node.right;
}
}
}
//后序遍历
function stackPostTravel(nodeTree){
if(!nodeTree) return;
let stack=[];
let node=nodeTree;
while(node||stack.length){
while(node){
stack.push(node);
node=node.left;
}
}
if(stack.length){
node=stack.pop();
if(node.right){
stack.push(node);
node=node.right;
}
console.log(stack.data);
}
}
(2)广度优先遍历-队列实现(先进先出)
/*使用队列实现广度优先遍历*/
function levelOrderTravel(nodeTree){
if(!nodeTree) return;
//初始化一个队列
let queue=[];
//将根节点入队
queue.push(nodeTree);
let node=null;
while(queue.length){
//先进先出
node=queue.shift();
console.log(node.data);
//如果出队节点存在左节点,则入队
if(node.left){ queue.push(node.left);}
//右边同上
if(node.right){ queue.push(node.right);}
}
}
console.log(levelOrderTravel(tree));
三、KMP算法
实现步骤:
1、构建最大长度表
2、构建next数组,计算移动位数(可与1合并)
3、KMP搜索算法实现
//1、构建最大长度表
function generatePrefixTable(pattern){
var prefix_table=[];
var len=0; // 最少公共前后缀长度初始化为0
prefix_table[0]=len; //初始化i=0,len=0
var i=1;
var n=pattern.length;
while(i<n){
if(pattern[len]===pattern[i]){
len++;
prefix_table[i]=len;
i++;
}else{
if(len>0){ //侧移
len=prefix_table[len-1];
}else{
prefix_table[i]=0;
i++;
}
}
}
return prefix_table;
}
//2、构建next数组
function generateNextArr(prefix_table){
for(var i=prefix_table.length-1;i>0;i--){
prefix_table[i]=prefix_table[i-1];
}
prefix_table[0]= -1;
}
//3、KMP搜索算法的实现
function kmp(str,pattern){
var prefix_table=generatePrefixTable(pattern);
generateNextArr(prefix_table);
var i=0; //str 指针
var j=0; pattern指针
while(i<str.length&&j<pattern.length){
if(str[i]==pattern[j]){
i++;
j++;
}else{
j=prefix_table[j];
if(j===-1){
i++;
j++;
}
}
}
if(j===pattern.length){
return i-j;
}else{
return -1;
}
}
kmp("bbc abcdab abcdabcdabde", "cdabd") // 结果输出为17,正确
KMP算法优化
直接构建next数组
function generateNextArr(pattern){
var i=0;
var j=-1;
var next=[];
next[0]=-1;
while(i<pattern.length){
if(j===-1||pattern[i]===pattern[j]){
i++;
j++;
next[i]=j;
}else{
j=next[j];
}
}
return next;
}
function kmp(str,pattern){
var next=generateNextArr(pattern);
var i=0;
var j=0;
while(i>str.length&&j<pattern.length){
if(str[i]===pattern[j]||j===-1){
i++;
j++;
}else{
j=next[j];
}
}
if(j===pattern.length){
return i-j;
}else{
return -1;
}
}