算法入门学习笔记

44 阅读24分钟

时间复杂度的计算规则

  • 如果一个算法执行的操作数与输入数据的规模N的表达式是多项式

    ​ aN² + bN + c

  • 时间复杂度忽略加法常数项

  • 只保留最高次幂项

  • 并且最高次幂项的乘法系数看做1

可以容纳数据的结构被称为数据结构

算法是用来对数据结构进行处理的方法

数据结构是静态的

算法是动态的

数组特性:

操作系统小知识:通过偏移查询数据性能好。

  1. 存储在物理空间上是连续的。
  2. 底层的数组长度是不可变的。
  3. 数组的变量,指向了数组第一个元素的位置。

优点:查询性能好。指定查询某个位置。

缺点:

  1. 因为空间必须是连续的,所以如果数组比较大,当系统空间碎片较多的时候,容易存不下。
  2. 因为数组的长度是固定的,所以数组的内容难以被添加和删除。

链表特性:

想传递一个链表,必须传递链表的根节点。

每一个节点,都认为自己是根节点。

  1. 空间上不是连续的。
  2. 每存放一个值,都要多开销一个引用空间。

优点:

  1. 只要内存足够大,就能存的下,不用担心空间碎片的问题。
  2. 链表的添加和删除非常的容易。

image-20240514135252417.png

缺点:

  1. 查询速度慢,(指的是查询某个位置。)
  2. 链表每一个节点都需要创建一个指向next的引用,浪费一些空间。

当节点内数据越多的时候,这部分多开销的内存影响越小。

//创建一个链表
function Node(value){
	this.value = value;
	this.next = null;
}
var a = new Node(1);
var b = new Node(2);
var c = new Node(3);
var d = new Node(4);
a.next = b;
b.next = c;
c.next = d;
console.log(a.value); //1
console.log(a.next.value); //2
console.log(a.next.next.value); //3
console.log(a.next.next.next.value); //4

线性数据结构的遍历

将一个集合中的每一个元素进行获取并查看

//创建一个链表
function Node(value){
	this.value = value;
	this.next = null;
}
var a = new Node(1);
var b = new Node(2);
var c = new Node(3);
var d = new Node(4);
a.next = b;
b.next = c;
c.next = d;
//遍历链表
function bianLink(root){
	var temp = root;
	while(true){
		if(temp != null){
			console.log(temp.value);
		}else{
			break;
		}
		temp = temp.next;
	}
}

bianLink(a);
//递归遍历,必须有出口
function bianLink(root){
	if(root == null) return;
	console.log(root.value);
	bianLink(root.next);
}

链表的逆置

function Node(value) {
	this.value = value;
	this.next = null;
}

var node1 = new Node(1);
var node2 = new Node(2);
var node3 = new Node(3);
var node4 = new Node(4);
var node5 = new Node(5);

node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;

function nizhi(root) {
    if(root.next.next == null) { //代表当前节点是倒数第二个节点
        root.next.next = root; //让最后一个节点指向自己(倒数第二个节点)
        return root.next;
    } else {
        var result = nizhi(root.next);
        root.next.next = root;
        root.next = null;
        return result;
    }
}

var newRoot = nizhi(node1);

function bianLink(root){
    if(root == null) return;
    console.log(root.value);
    bianLink(root.next);
}

递归排序

排序不是比较大小。

排序的本质是比较和交换。

var arr = [4,1,6,9,3,2,8,7];
function compare(a,b) {//比较之后需要得出是否需要交换
    if(a < b) return true;
    else return false;
}

function exchange(arr,a,b) {//将数组中ab位置里的值进行交换
    var temp = arr[a];
    arr[a] = arr[b];
    arr[b] = temp;
}
//冒泡排序
function sort(arr){//这个sort可以是冒泡排序也可以是选择排序也可以是其他排序
    for (var i = 0 ; i < arr.length ; i ++){
         for(var j = 0 ;j < arr.length - 1 - i ;j ++) {
        	if(compare(arr[j],arr[j+1])){
            	exchange(arr,j,j + 1);
        	}
    	} 
    }
}
//选择排序,内层循环,每一圈选出一个最大的,然后放在最后面
function sort(arr){
    for (var i = 0 ; i < arr.length ; i ++){
         for(var j = 0 ;j < arr.length - 1 - i ;j ++) {
             var maxIndex = 0;
        	if(compare(arr[maxIndex],arr[j])){
            	maxIndex = j;
        	}
    	} 
        exchange(arr,maxIndex,arr.length - 1 - i);
    }
}

//任何一种排序算法,都没有优劣之分,只有是否适合的场景

sort(arr);
console.log(arr);
//简单的快速排列
var arr = [4,1,6,9,3,2,8,7];

function quickSort(arr) {
    if (arr == null || arr.length == 0 ) return [];
    //选班长
    var leader = arr[0];
    //小的在左边,大的在右边
    var left = [];
    var right = [];
    for (var i = 1 ; i < arr.length ; i++) {
        if (arr[i] < leader) left.push(arr[i]);
        else right.push(arr[i]);
    }
    left = quickSort(left);
    right = quickSort(right);
    left.push(leader);
    return left.concat(right);
}
console.log(quickSort(arr));
//标准的快速排列
var arr = [4,1,6,9,3,2,8,7];

	function swap(arr, a, b) {
		var temp = arr[a];
		arr[a] = arr[b];
		arr[b] = temp;
	}

	function quickSort2(arr, begin, end) {
		if (begin >= end - 1) return;
		var left = begin;
		var right = end;
		do {
			do left++;
			while (left < right && arr[left] < arr[begin]);
			do right--;
			while (left < right && arr[right] > arr[begin]);
			if (left < right) swap(arr, left, right);
		} while (left < right); //左边比右边小继续执行
		var swapPoint = left == right ? right - 1 : right;
		swap(arr, begin, swapPoint);
		quickSort2(arr, begin, swapPoint);
		quickSort2(arr, swapPoint + 1, end);
	}

	function quickSort(arr) {
		quickSort2(arr, 0, arr.length);
	}
	quickSort(arr);
	console.log(arr);

栈和队列

栈结构特点:先入后出,类比成一个箱子,先放进去的,被压在下面。

image-20240515093755252.png

	//栈结构
	var arr = [];

	function Stack() {
		this.arr = [];
		this.push = function (value) {
		   arr.push(value);
		};
		this.pop = function () {
		  return arr.pop();
		}
	}
	var stack = new Stack();
	stack.push(1);
	stack.push(2);
	stack.push(3);
	console.log(stack.arr); // [1, 2, 3]
	stack.pop();
	console.log(stack.arr); // [1, 2]

队列结构特点:先入先出,可以类比成一个管道

image-20240515093850227.png


function Queue() {
    this.arr = [];
    this.push = function (value) {
        this.arr.push(value);
    }
    this.pop = function () {
        return this.arr.shift();
    }
}

var queue = new Queue();
queue.push(1);
queue.push(2);
queue.push(3);
console.log(queue.arr);//[1,2,3]
queue.pop();
console.log(queue.arr);//[2,3]

双向链表

function Node(value) {
 this.value;
 this.next = null;
 this.pre = null;
}

var node1 = new Node(1);
var node2 = new Node(2);
var node3 = new Node(3);
var node4 = new Node(4);
var node5 = new Node(5);

node1.next = node2;
node2.pre = node1;
node2.next = node3;
node3.pre = node2;
node3.next = node4;
node4.pre = node3;
node5.pre = node4;

优点:无论给出哪一个节点,都能对整个链表进行遍历

缺点:多耗费一个引用空间,而且构建双向链表比较复杂

二维数组结构

image-20240515101400694.png

var arr = new Array(4);
for (var i = 0; i < arr.length ; i ++ ){
	arr[i] = new Array(8);
}

二维拓扑结构 (图)

表示一个图可以使用点集合和边集合

点集合:[a,b,c,d,e,f]

image-20240515102057205.png

function Node(value) {
	this.value = value;
	this.neighbor = [];
}

var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");
var f = new Node("f");

a.neighbor.push(b);
a.neighbor.push(c);
a.neighbor.push(f);

b.neighbor.push(e);
b.neighbor.push(d);
b.neighbor.push(a);

树形结构--有向无环图

树是图的一种

树形结构有一个根节点

树形结构没有回路

image-20240515103712012.png

根节点:A。

叶子节点:下面没有其他节点了。

节点:既不是根节点,又不是叶子节点的普通节点。

树的度:这棵树有最多叉的节点有多少个叉这棵树的度就为多少。

树的深度:树最深有几层,树的深度就为几。

二叉树

树的度最多为2的树形结构

image-20240515104348189.png

  1. 二叉树的根节点A
  2. 子节点:某个节点下面的节点
  3. 父节点:上级节点
  4. 叶子节点:CDE
  5. 节点:B

满二叉树:

  1. 所有的叶子节点都在最底层
  2. 每个非叶子节点都有两个子节点

image-20240515104551832.png

完全二叉树

国内定义:

  1. 叶子节点都在最后一层或倒数第二层
  2. 叶子节点都向左聚拢

image-20240515110127251.png

国际定义:

  1. 叶子节点都在最后一层或倒数第二层
  2. 如果有叶子节点,就必然有两个叶子节点

二叉树中子树的概念

在二叉树中,每个节点都认为自己是根节点。

子树:二叉树中,每一个节点或叶子节点,都是一颗子树的根节点。

左子树、右子树:

二叉树的遍历

传递二叉树要传根节点。

前序遍历:(先根次序遍历)

先打印当前的,再打印左边的,再打印右边的。

中序遍历:(中根次序遍历)

先打印左边的,再打印当前的,再打印右边的。

后序遍历:(后根次序遍历)

先打印左边的,再打印右边的,再打印中间的。

image-20240515104551832.png

前序遍历:ACFGBDE

中序遍历:FCGADBE

后序遍历:FGCDEBA

前序遍历
function Node(value) {
    this.value = value;
    this.left = null;
    this.right = null;
}

var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");
var f = new Node("f");
var g = new Node("g");

a.left = c;
a.right = b;
c.left = f;
c.right = g;
b.left = d;
b.right = e;

function fn1(root) {
    if( root == null ) return;
    console.log(root.value);
    fn1(root.left);
    fn1(root.right);
}

fn1(a);
中序遍历
 function Node(value) {
     this.value = value;
     this.left = null;
     this.right = null;
 }

var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");
var f = new Node("f");
var g = new Node("g");

a.left = c;
a.right = b;
c.left = f;
c.right = g;
b.left = d;
b.right = e;

function f1(root) {
    if(root == null) return;
    f1(root.left);
    console.log(root.value);
    f1(root.right);
}

f1(a);
后序遍历
 function Node(value) {
     this.value = value;
     this.left = null;
     this.right = null;
 }

var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");
var f = new Node("f");
var g = new Node("g");

a.left = c;
a.right = b;
c.left = f;
c.right = g;
b.left = d;
b.right = e;

function f1(root) {
    if(root == null) return;
    f1(root.left);
    f1(root.right);
    console.log(root.value);
}

f1(a);
根据前序中序还原二叉树
var qian = ["a","c","f","g","b","d","e"];
var zhong = ["f","c","g","a","d","b","e"];

function Node(value) {
    this.value = value;
    this.left = null;
    this.right = null;
}

function f1(qian,zhong){
    if(qian == null || zhong == null || qian.length == 0 || zhong.length == 0 || qian.length != zhong.length) return null;
    var root = new Node(qian[0]);
    var index = zhong.indexOf(root.value);//找到根节点再中序中所在的位置
    var qianLeft = qian.slice(1, 1 + index);//前序遍历的左子树
    var qianRight = qian.slice(1 + index, qian.length);//前序遍历的右子树
    var zhongLeft = zhong.slice(0, index);//中序遍历左子树
    var zhongRight = zhong.slice(1 + index, zhong.length);//中序遍历右子树
    root.left = f1(qianLeft,zhongLeft);//根据左子树的前序和中序还原左子树并赋值给root.left
    root.right = f1(qianRight,zhongRight);//根据右子树的前序和中序还原右子树并复制给root.right
    return root;
}

var root = f1(qian,zhong);
console.log(root.left);
console.log(root.right);
根据中序后序还原二叉树
var zhong = ["f","c","g","a","d","b","e"];
var hou = ["f","g","c","d","e","b","a"];

function Node(value) {
    this.value = value;
    this.left = null;
    this.right = null;
}

function f1(zhong,hou) {
    if(zhong == null || hou == null || zhong.length == 0 || hou.length == 0 || zhong.length != hou.length) return null;
    var root = new Node(hou[hou.length - 1]);
    var index = zhong.indexOf(root.value);
    var zhongLeft = zhong.slice(0,index);
    var zhongRight = zhong.slice(index + 1,zhong.length);
    var houLeft = hou.slice(0,index);
    var houRight = hou.slice(index,hou.length - 1);
    root.left = f1(zhongLeft,houLeft);
    root.right = f1(zhongRight,houRight);
    return root;
}

var root = f1(zhong,hou);
console.log(root.left);
console.log(root.right);

二叉树的搜索

树的搜索,图的搜索,爬虫的逻辑,搜索引擎的爬虫算法。

深度优先搜索

更适合探索未知 先搜A-C-F如果没有返回C再搜G

function Node(value) {
    this.value = value;
    this.left = null;
    this.right = null;
}

var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");
var f = new Node("f");
var g = new Node("g");

a.left = c;
a.right = b;
c.left = f;
c.right = g;
b.left = d;
b.right = e;

//对于二叉树来说,深度优先搜索,和前序遍历的顺序是一样的。
function deepSearch(root,target) {
    if(root == null) return false;
    if(root.value == target) return true;
    var left = deepSearch(root.left,target);
    var right = deepSearch(root.right,target);
    return left || right;
}
console.log(deepSearch(a,"g"));
广度优先搜索

更适合探索局域 先搜A-C没有就搜B 横向搜

function Node(value) {
    this.value = value;
    this.left = null;
    this.right = null;
}

var a = new Node("a");
var b= new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");
var f = new Node("f");
var g = new Node("g");

a.left = c;
a.right = b;
c.left = f;
c.right = g;
b.left = d;
b.right = e;

function f1(rootList,target) {
    if(rootList == null || rootList.length == 0) return false;
    var childList = [];//当前层所有节点的子节点,都在这个list中,这样传入下一层级的时候,就可以遍历整个层级的节点
    for (var i = 0 ;i < rootList.length ; i++){
        if(rootList[i] != null && rootList[i].value == target){
            return true;
        }else if(rootList[i] != null && rootList[i].value != target) {
            childList.push(rootList[i].left);
            childList.push(rootList[i].right);
        }
    }
    return f1(childList,target);
}

console.log(f1([a],'g'));

二叉树的比较

遇到二叉树比较问题是,必须要确定,左右两棵子树如果交换位置,既左右两边互换,算不算同一颗二叉树。

如果没有特殊说明左右呼唤还是同一棵树,那么默认互换后不是同一颗树。

function Node(value) {
    this.value = value;
    this.left = null;
    this.right = null;
}

var a1 = new Node("a");
var b1 = new Node("b");
var c1 = new Node("c");
var d1 = new Node("d");
var e1 = new Node("e");
var f1 = new Node("f");
var g1 = new Node("g");
a1.left = c1;
a1.right = b1;
c1.left = f1;
c1.right = g1;
b1.left = d1;
b1.right = e1;

var a2 = new Node("a");
var b2 = new Node("b");
var c2 = new Node("c");
var d2 = new Node("d");
var e2 = new Node("e");
var f2 = new Node("f");
var g2 = new Node("g");

a2.left = c2;
a2.right = b2;
c2.left = f2;
c2.right = g2;
b2.left = d2;
b2.right = e2;

function compareTree(root1,root2) {
    if(root1 == root2) return true;//是同一棵树
    if(root1 == null && root2 != null || root1 != null && root2 == null) return false;//有一方为null
    if(root1.value != root2.value) return false;//相同位置的值不相等
    var leftBool = compareTree(root1.left,root2.left);//判断左子树是否相等
    var rightBool = compareTree(root1.right,root2.right);//判断右子树是否相等
    return leftBool && rightBool;//必须左右子树都相等
}

console.log(compareTree(a1,a2));
//互换后还是同一颗树
//....//省略部分代码
function compareTree(root1,root2) {
   	if(root1 == root2) return true;
   	if(root1 == null && root2 != null || root2 == null && root1 != null) return false;
    if(root1.value != root2.value) return false;
    return compareTree(root1.left,root2.left) && compareTree(root2.right,root1.right) || compareTree(root1.left,root2.right) && compareTree(root1.right,root2.left);
}

二叉树的diff算法

新增了什么 ,修改了什么,删除了什么

//{type:"新增",origin:null,now:c2},
//{type:"修改",origin:c1,now:c2},
//{type:"删除",origin:c2,now:null}

function Node(value) {
    this.value = value;
    this.left = null;
    this.right = null;
}

var a1 = new Node("a");
var b1 = new Node("b");
var c1 = new Node("c");
var d1 = new Node("d");
var e1 = new Node("e");
var f1 = new Node("f");
var g1 = new Node("g");
a1.left = c1;
a1.right = b1;
c1.left = f1;
c1.right = g1;
b1.left = d1;
b1.right = e1;

var a2 = new Node("a");
var b2 = new Node("b");
var c2 = new Node("c");
var d2 = new Node("d");
var e2 = new Node("e");
var f2 = new Node("f");
var g2 = new Node("g");

a2.left = c2;
a2.right = b2;
c2.left = f2;
c2.right = g2;
b2.left = d2;
b2.right = e2;

function diffTree(root1,root2,diffList) {
    if(root1 == root2) return diffList;
    if(root1 == null && root2 != null) {//新增了节点
        diffList.push({type:"新增",origin:null,now:root2});
    }else if(root1 != null && root2 == null) {//删除了节点
        diffList.push({type:"删除",origin:root1,now:null});
    }else if(root1.value != root2.value) {//相同位置修改了节点
        diffList.push({type:"修改",origin:root1,now:root2});
        diffTree(root1.left,root2.left,diffList);
        diffTree(root1.right,root2.right,diffList);
    }else{
        diffTree(root1.left,root2.left,diffList);
        diffTree(root1.right,root2.right,diffList);
    }
}

var diffList = [];
diffTree(a1,a2,diffList);
console.log(diffList);

图的最小生成树问题

image-20240515173955379.png

普利姆算法(加点法)

  1. 任选一个点作为起点
  2. 找到以当前选中点为起点路径最短的边
  3. 如果这个边的另一端没有被联通进来,那么就连结
  4. 如果这个边的另一端也早就被连进来了,则看倒数第二短的边
  5. 重复2-4直到将所有的点都连上为止

image-20240516091900594.png

var max = 1000000;
var pointSet = [];
var distance = [
  [0,4,7,max,max],
  [4,0,8,6,max],
  [7,8,0,5,max],
  [max,6,5,0,7],
  [max,max,max,7,0]
];

function Node(value) {
    this.value = value;
    this.neighbor = [];
}

var a = new Node("A");
var b = new Node("B");
var c = new Node("C");
var d = new Node("D");
var e = new Node("E");

pointSet.push(a);
pointSet.push(b);
pointSet.push(c);
pointSet.push(d);
pointSet.push(e);

function getIndex(str) {
    for(var i = 0 ; i < pointSet.length ; i ++) {
        if(str == pointSet[i].value) return i;
    }
    return -1;
}

//需要传入点的集合,边的集合,当前已经连接进入的集合
//此方法,根据当前已经有的节点,来进行判断,获取到距离最短的点
function getMinDisNode(pointSet, distance , nowPointSet) {
    var fromNode = null;//线段的起点
    var minDisNode = null;//线段的终点
    var minDis = max;
    //根据当前已有的这些点为起点,依次判断连接其他的点的距离是多少
    for (var i = 0; i< nowPointSet.length ; i ++) {//遍历所有的起点
        var nowPointIndex = getIndex(nowPointSet[i].value);//获取当前节点的序号 
        for(var j = 0 ; j < distance[nowPointIndex].length ; j ++) {//遍历所有的行
            var thisNode = pointSet[j];//thisNode表示distance中的点,但是这个点不是对象
            if(nowPointSet.indexOf(thisNode) < 0//首先这个点不能是已经接入的点
              && distance[nowPointIndex][j] < minDis) {//其次点之间的距离是目前的最短距离
               		fromNode = nowPointSet[i];
                	minDisNode = thisNode;
                	minDis = distance[nowPointIndex][j];//更新最小距离
               }
        }
    }
    fromNode.neighbor.push(minDisNode);
    minDisNode.neighbor.push(fromNode);
    return minDisNode;
}

function prim(pointSet, distance, start) {
    var nowPointSet = [];
    nowPointSet.push(start);
    //获取最小代价的边
    while (true) {
        var minDisNode = getMinDisNode(pointSet, distance, nowPointSet);
        nowPointSet.push(minDisNode);
        if(nowPointSet.length == pointSet.length) {
            break;
        }
    }
}

prim(pointSet,distance,pointSet[2]);
console.log(pointSet);

克鲁斯卡尔算法(加边法)

  1. 选择最短的边进行连结
  2. 要保证连结的两端至少有一个点是新的点
  3. 或者这个边是将两个部落进行连结的
  4. 重复1-3直到所有的点都连结到一起

image-20240516115743991.png

var max = 1000000;
var pointSet = [];
var distance = [
    [0,4,7,max,max],
    [4,0,8,6,max],
    [7,8,0,5,max],
    [max,6,5,0,7],
    [max,max,max,7,0]
];

function Node(value) {
    this.value = value;
    this.neighbor = [];
}

var a = new Node("A");
var b = new Node("B");
var c = new Node("C");
var d = new Node("D");
var e = new Node("E");

pointSet.push(a);
pointSet.push(b);
pointSet.push(c);
pointSet.push(d);
pointSet.push(e);

function getIndex(str) {
    for (var i = 0 ; i < pointSet.length ; i ++) {
        if(str == pointSet[i].value) return i;
    }
    return -1;
}

//需要传入点的集合,边的集合,当前已经连接进入的集合
//此方法,根据当前已经有的节点,来进行判断,获取到距离最短的点
function getMinDisNode(pointSet, distance, nowPointSet) {
    var fromNode = null;//起点
    var minDisNode = null;//终点
    var minDis = max;
    //根据当前已有的这些点为起点,依次判断连接其他的点的距离是多少
    for (var i = 0; i < nowPointSet.length; i ++ ){
        var nowPointIndex = getIndex(nowPointSet[i].value);//获取当前节点的序号
        for (var j = 0; j < distance[nowPointIndex].length; j++) {
            var thisNode = pointSet[j];
            if (nowPointSet.indexOf(thisNode) < 0 //首先这个点不能是已经接入的点
               && distance[nowPointIndex][j] < minDis) {//其次点之间的距离得是目前的最近的
                	fromNode = nowPointSet[i];
                	minDisNode = thisNode;
                	minDis = distance[nowPointIndex][j];
                }
        }
    }
    fromNode.neighbor.push(minDisNode);
    minDisNode.neighbor.push(fromNode);
    return minDisNode;
}

function prim(pointSet, distance, start) {
    var nowPointSet = [];
    nowPointSet.push(start);
    //获取最小代价的边
    while (true) {
        var minDisNode = getMinDisNode(pointSet, distance, nowPointSet);
        nowPointSet.push(minDisNode);
        if ( nowPointSet.length == pointSet.length) {
            break;
        }
    }
}

prim(pointSet, distance, pointSet[2]);
console.log(pointSet);

二叉搜索树(二叉排序树)

首先这是一颗二叉树

其次有排序的效果,左子树的节点都比当前节点小,右子树的节点都比当前节点大

//问题:有一万个数,写一个方法,进行查找。查找给定的树,返回存在还是不存在。
//要求:尽可能的性能好。
//构建二叉搜索树
var arr = [];

for (var i = 0 ; i < 10000 ; i ++) {
    arr[i] = Math.floor(Math.random() * 10000);
}

var num = 0;

function search(arr,target) {
    for(var i = 0 ; i < arr.length ; i ++) {
        num += 1;
        if(arr[i] == target) return true;
    }
    return false;
}

function Node(value) {
    this.value = value;
    this.left = null;
    this.right = null;
}

function addNode(root,num) {
 	if(root == null) return;
    if(root.value == num) return;
    if(root.value < num) {//目标值比当前节点大
        if(root.right == null) root.right = new Node(num);//如果右侧为空,则创建右子树
        else addNode(root.right,num);//如果右侧不为空,则向右侧进行递归
    }else {//目标比当前节点小
        if(root.left == null) root.left = new Node(num);
        else addNode(root.left,num);
    }
}

function buildSearchTree(arr) {
    if(arr == null || arr.length == 0) return null;
    var root = new Node(arr[0]);
    for (var i = 0 ; i < arr.length ; i++) {
        addNode(root,arr[i]);
    }
    return root;
}

var num2 = 0;

//搜索树的使用
function searchByTree (root,target) {
    if(root == null) return false;
     num2 += 1;
    if(root.value == target) return true;
    if(root.value > target) return searchByTree(root.left,target);
    else return searchByTree(root.right,target);
}

console.log(search(arr,1000));
console.log(num);
var root = buildSearchTree(arr);
console.log(searchByTree(root,1000));
console.log(num2)

平衡二叉树

  1. 根节点的左子树与右子树的高度差不能超过1
  2. 这颗二叉树的每个子树都符合第一条

image-20240516172239081.png

//代码实现判断平衡二叉树
function Node(value) {
    this.value = value;
    this.left = null;
    this.right = null;
}

var a = new Node("A");
var b = new Node("B");
var c = new Node("C");
var d = new Node("D");
var e = new Node("E");
var f = new Node("F");
var g = new Node("G");
var h = new Node("H");
var j = new Node("J");

a.left = b;
a.right = c;
b.left = d;
b.right = e;
c.left = f;
c.right = g;
d.right = h;
e.right = j;

function getDeep(root) {
    if(root == null) return 0;
    var leftDeep = getDeep(root.left);
    var rightDeep = getDeep(root.right);
    return Math.max(leftDeep,rightDeep) + 1;
}

function isBalance(root) {
    if(root == null) return true;
    var leftDeep = getDeep(root.left);
    var rightDeep = getDeep(root.right);
    if(Math.abs(leftDeep - rightDeep) > 1){//不平衡
        return false;
    }else {
        return isBalance(root.left) && isBalance(root.right);
    }
}

console.log(isBalance(a));

二叉树的单旋操作(左单旋、右单旋)

某一节点不平衡

如果左边浅,右边深,进行左单旋

旋转节点:不平衡的节点为旋转节点(2)

新根:旋转之后成为根节点的节点(5)

变化分支:父级节点发生变化的那个分支

不变分支:父级节点不变的那个分支。

image-20240516174550668.png

9d20485114e628032e30bd5f16012e80.png

左单旋时:

旋转节点:当前不平衡的节点

新根:右子树的根节点

变化分支:旋转节点的右子树的左子树

不变分支:旋转节点的右子树的右子树

image-20240516174550668.png

右单旋时:

旋转节点:当前不平衡的点

新根:左子树的根节点

变化分支:旋转节点的左子树的右子树

不变分支:旋转节点的右子树的左子树

image-20240516175003033.png

进行左单旋

  1. 找到新根
  2. 找到变化分支
  3. 当前旋转节点的右孩子为变化分支
  4. 新根的左孩子为旋转节点
  5. 返回新的根节点

image-20240516175311969.png image-20240516175327422.png

function Node(value) {
    this.value = value;
    this.left = null;
    this.right = null;
}

var node2 = new Node("2");
var node5 = new Node("5");
var node3 = new Node("3");
var node6 = new Node("6");

node2.right = node5;
node5.left = node3;
node5.right = node6;

function getDeep(root) {
    if(root == null) return 0;
    var leftDeep = getDeep(root.left);
    var rightDeep = getDeep(root.right);
    return Math.max(leftDeep,rightDeep) + 1;
}

function isBalance(root) {
    if(root == null) return true;
    var leftDeep = getDeep(root.left);
    var rightDeep = getDeep(root.right);
    if(Math.abs(leftDeep - rightDeep) > 1){//不平衡
        return false;
    }else {
        return isBalance(root.left) && isBalance(root.right);
    }
}

function leftRotate(root) {
    // 找到新根
    var newRoot = root.right;
	// 找到变化分支
    var changeTree = root.right.left;
	// 当前旋转节点的右孩子为变化分支
    root.right = changeTree;
	// 新根的左孩子为旋转节点
    newRoot.left = root;
	// 返回新的根节点
    return newRoot;
}

function rightRotate(root) {
    // 找到新根
    var newRoot = root.left;
	// 找到变化分支
    var changeTree = root.left.right;
	// 当前节点的左孩子为变化分支
    root.left = changeTree;
	// 新根的右节点为旋转节点
    newRoot.right = root;
	// 返回新的根节点
    return newRoot;
}

function change(root) {//返回平衡之后的根节点
    if (isBalance(root)) return root;
    if (root.left != null) root.left = change(root.left);
    if(root.right != null) root.right = change(root.right);
    var leftDeep = getDeep(root.left);
    var rightDeep = getDeep(root.right);
    if(Math.abs(leftDeep - rightDeep) < 2) {
        return root;
    }else if (leftDeep > rightDeep) {//不平衡,左边深,进行右旋
        return rightRotate(root);
    }else {//右边深,进行左旋
        return leftRotate(root);
    }
    return root;
}

console.log(isBalance(node2));
var newRoot = change(node2);
console.log(isBalance(newRoot));
console.log(newRoot);

进行右单旋

  1. 找到新根
  2. 找到变化分支
  3. 当前节点的左孩子为变化分支
  4. 新根的右节点为旋转节点
  5. 返回新的根节点

二叉树的双旋(左右双旋,右左双旋)

变化分支,不可以是唯一的最深分支,

如果变化分支是唯一的最深分支,

要先进行反向的旋转

当要对某个节点进行左单旋时,

如果变化分支是唯一的最深分支,那么我们要对新根进行右单旋

然后再进行左单旋

这样的旋转叫做右左双旋

当要对某个节点进行右单旋时

如果变化分支是唯一的最深分支,那么我们要对新根进行左单旋

然后再进行右单旋

这样的旋转叫做左右双旋

image-20240517094248218.png

image-20240517094323702.png

function Node(value) {
    this.value = value;
    this.left = null;
    this.right = null;
}

var node8 = new Node("8");
var node7 = new Node("7");
var node6 = new Node("6");
var node5 = new Node("5");
var node2 = new Node("2");

node8.left = node7;
node7.left = node6;
node6.left = node5;
node5.left = node2;

function getDeep(root) {
    if(root == null) return 0;
    var leftDeep = getDeep(root.left);
    var rightDeep = getDeep(root.right);
    return Math.max(leftDeep,rightDeep) + 1;
}

function isBalance(root) {
    if(root == null) return true;
    var leftDeep = getDeep(root.left);
    var rightDeep = getDeep(root.right);
    if(Math.abs(leftDeep - rightDeep) > 1){//不平衡
        return false;
    }else {
        return isBalance(root.left) && isBalance(root.right);
    }
}

function leftRotate(root) {
    // 找到新根
    var newRoot = root.right;
	// 找到变化分支
    var changeTree = root.right.left;
	// 当前旋转节点的右孩子为变化分支
    root.right = changeTree;
	// 新根的左孩子为旋转节点
    newRoot.left = root;
	// 返回新的根节点
    return newRoot;
}

function rightRotate(root) {
    // 找到新根
    var newRoot = root.left;
	// 找到变化分支
    var changeTree = root.left.right;
	// 当前节点的左孩子为变化分支
    root.left = changeTree;
	// 新根的右节点为旋转节点
    newRoot.right = root;
	// 返回新的根节点
    return newRoot;
}

function change(root) {//返回平衡之后的根节点
    if (isBalance(root)) return root;
    if (root.left != null) root.left = change(root.left);
    if(root.right != null) root.right = change(root.right);
    var leftDeep = getDeep(root.left);
    var rightDeep = getDeep(root.right);
    if(Math.abs(leftDeep - rightDeep) < 2) {
        return root;
    }else if (leftDeep > rightDeep) {//不平衡,左边深,进行右旋
        var changeTreeDeep = getDeep(root.left.right);
        var noChangeTreeDeep = getDeep(root.left.left);
        if (changeTreeDeep > noChangeTreeDeep) {
            root.left = leftRotate(root.left);
        }
        return rightRotate(root);
    }else {//右边深,进行左旋
        var changeTreeDeep = getDeep(root.right.left);
        var noChangeTreeDeep = getDeep(root.right.right);
        if (changeTreeDeep > noChangeTreeDeep) {
            root.right = rightRotate(root.right);
        }
        return leftRotate(root);
    }
    return root;
}

console.log(isBalance(node8));
var newRoot = change(node8);
console.log(isBalance(newRoot));
console.log(newRoot);

二叉树的双旋(右右双旋、左左双旋)

如果变化分支的高度比旋转节点的另一侧高度差距超过2

那么单旋依旧不平衡

function Node(value) {
    this.value = value;
    this.left = null;
    this.right = null;
}

var node8 = new Node("8");
var node7 = new Node("7");
var node6 = new Node("6");
var node5 = new Node("5");
var node2 = new Node("2");

node8.left = node7;
node7.left = node6;
node6.left = node5;
node5.left = node2;

function getDeep(root) {
    if(root == null) return 0;
    var leftDeep = getDeep(root.left);
    var rightDeep = getDeep(root.right);
    return Math.max(leftDeep,rightDeep) + 1;
}

function isBalance(root) {
    if(root == null) return true;
    var leftDeep = getDeep(root.left);
    var rightDeep = getDeep(root.right);
    if(Math.abs(leftDeep - rightDeep) > 1){//不平衡
        return false;
    }else {
        return isBalance(root.left) && isBalance(root.right);
    }
}

function leftRotate(root) {
    // 找到新根
    var newRoot = root.right;
	// 找到变化分支
    var changeTree = root.right.left;
	// 当前旋转节点的右孩子为变化分支
    root.right = changeTree;
	// 新根的左孩子为旋转节点
    newRoot.left = root;
	// 返回新的根节点
    return newRoot;
}

function rightRotate(root) {
    // 找到新根
    var newRoot = root.left;
	// 找到变化分支
    var changeTree = root.left.right;
	// 当前节点的左孩子为变化分支
    root.left = changeTree;
	// 新根的右节点为旋转节点
    newRoot.right = root;
	// 返回新的根节点
    return newRoot;
}

function change(root) {//返回平衡之后的根节点
    if (isBalance(root)) return root;
    if (root.left != null) root.left = change(root.left);
    if(root.right != null) root.right = change(root.right);
    var leftDeep = getDeep(root.left);
    var rightDeep = getDeep(root.right);
    if(Math.abs(leftDeep - rightDeep) < 2) {
        return root;
    }else if (leftDeep > rightDeep) {//不平衡,左边深,进行右旋
        var changeTreeDeep = getDeep(root.left.right);
        var noChangeTreeDeep = getDeep(root.left.left);
        if (changeTreeDeep > noChangeTreeDeep) {
            root.left = leftRotate(root.left);
        }
        var newRoot =  rightRotate(root);
        newRoot.right = change(newRoot.right);
        newRoot = change(newRoot);
        return newRoot;
    }else {//右边深,进行左旋
        var changeTreeDeep = getDeep(root.right.left);
        var noChangeTreeDeep = getDeep(root.right.right);
        if (changeTreeDeep > noChangeTreeDeep) {
            root.right = rightRotate(root.right);
        }
        var newRoot = leftRotate(root);
        newRoot.left = change(newRoot.left);
        newRoot = change(newRoot);
        return newRoot;
    }
    return root;
}

console.log(isBalance(node8));
var newRoot = change(node8);
console.log(isBalance(newRoot));
console.log(newRoot);

234树的由来

我们希望有一个树,最多有四个叉(度为4)。

234树子节点永远在最后一层

234树永远是平衡的(每一个路径高度都相同)

达成了一定的效果

分支变多了,层数变少了

节点中存的树变多了,节点变少了。

因为分支变多了,所以复杂度上升了

希望对二三四树进行简化。

希望能简化为二叉树

希望依旧保留多叉

希望依旧单节点中存放多个值

二叉平衡排序树性能是极致吗?不是

如果我们要提升二叉平衡排序树的性能该如何做?

影响二叉平衡排序树的性能的点在哪?

答:在于二叉平衡排序树智能有两个叉,导致在节点铺满的时候也会有很多层。

希望可以一个节点存多个数。可以提升空间的性能。

如何才能让查找的效率尽可能少?

答:树的层级越少,查找效率越高。

怎么样才能让二叉平衡排序树的层数变得更少?

答:如果不是二叉,层数会更少。

叉越多,层数越少,但是叉越多,树的结构就越复杂。(4)

红黑树

性质1.每个节点要么是红色,要么是黑色。(红色是啥,黑色是啥?)

性质2.根节点必须是黑色。(为啥?黑色节点代表啥?)

性质3.每个红色节点的两个子节点都必须是黑色(即不存在两个连续的红色节点)。(红色是啥,黑色是啥?)

性质4.从任一节点到其每个叶子节点的所有路径都包含相同数量的黑色节点。(为什么不算上红色节点呢)

树的深度优先搜索

image-20240517144157547.png

function Node(value) {
    this.value = value;
    this.childs = [];
}

var a = new Node("A");
var b = new Node("B");
var c = new Node("C");
var d = new Node("D");
var e = new Node("E");
var f = new Node("F");

a.childs.push(c);
a.childs.push(f);
a.childs.push(b);
b.childs.push(d);
b.childs.push(e);

function deepSearch(root, target) {
    if(root == null || root.length == 0) return false;
    if(root.value == target) return true;
    var result = false;
    for (var i = 0 ; i < root.childs.length ; i ++) {
        result |= deepSearch(root.childs[i], target);
    }
    return result ? true : false;
}

console.log(deepSearch(a,"C"));

树的广度优先搜索

function Node(value) {
    this.value = value;
    this.childs = [];
}

var a = new Node("A");
var b = new Node("B");
var c = new Node("C");
var d = new Node("D");
var e = new Node("E");
var f = new Node("F");

a.childs.push(c);
a.childs.push(f);
a.childs.push(b);
b.childs.push(d);
b.childs.push(e);

function bfs(roots,target) {
    if(roots == null || roots.length == 0) return false;
    var childs = [];
    for (var i = 0 ; i < roots.length ; i ++) {
        if(roots[i].value == target) {
            return true;
        }else {
            childs = childs.concat(roots[i].childs);
        }
    }
    return bfs(childs,target);
}

console.log(bfs([a],'c'))

图的深度优先搜索

image-20240517150358261.png

function Node(value) {
    this.value = value;
    this.neighbor = [];
}

var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");

a.neighbor.push(b);
a.neighbor.push(c);
b.neighbor.push(a);
b.neighbor.push(c);
b.neighbor.push(d);
c.neighbor.push(a);
c.neighbor.push(b);
c.neighbor.push(d);
d.neighbor.push(b);
d.neighbor.push(c);
d.neighbor.push(e);
e.neighbor.push(d);

function deepSearch(node, target, path) {
    if(node == null) return false;
    if(path.indexOf(node) > -1) return false;
    if(node.value == target) return true;
    path.push(node);
    var result = false;
    for (var i  = 0 ; i < node.neighbor.length ; i ++) {
        result |= deepSearch(node.neighbor[i], target, path);
    }
    return result ? true : false;
}

console.log(deepSearch(b,"c",[]));

图的广度优先搜索

function Node(value) {
    this.value = value;
    this.neighbor = [];
}

var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");

a.neighbor.push(b);
a.neighbor.push(c);
b.neighbor.push(a);
b.neighbor.push(c);
b.neighbor.push(d);
c.neighbor.push(a);
c.neighbor.push(b);
c.neighbor.push(d);
d.neighbor.push(b);
d.neighbor.push(c);
d.neighbor.push(e);
e.neighbor.push(d);

function bfs(nodes, target, path) {
   if(nodes == null || nodes.length == 0) return false;
    var nextNode = []
   for (var i = 0 ; i < nodes.length ; i ++) {
       if(path.indexOf(nodes[i]) > -1) continue;
       path.push(nodes[i]);
       if(nodes[i].value == target) return true;
       else nextNode = nextNode.concat(nodes[i].neighbor);
   }
    return bfs(nextNode, target, path);
}

console.log(bfs([c],"b",[]));

动态规划之斐波那契数列

0,1,1,2,3,5,8,13,21.....

给出第n位,问第n位的值为多少

//f(n) = f(n-1) + f(n-2);
function fibo(n) {
    if(n <= 0) return -1;
    if(n == 1) return 0;
    if(n == 2) return 1;
    var a = 0;
    var b = 1;
    var c = 0;
    for (var i = 3 ; i <= n ; i ++) {
        c = a + b;
        a = b;
        b = c;
    }
    return c;
}

function fibo2 (n) {
    if(n <= 0) return -1;
    if(n == 1) return 0;
    if(n == 2) return 1;
    return fibo2(n-1) + fibo2(n-2);
}

console.log(fibo(5));
console.log(fibo2(5));

动态规划之青蛙跳台阶问题

一个青蛙,一次只能跳一级台阶,或者跳两级台阶

问:这个青蛙跳上n级台阶有多少种跳法

//如果这只青蛙,跳上了第n级台阶,那么最后一次跳跃之前,他一定在n-1级台阶或n-2级台阶上。
//那么跳上n级台阶有多少种情况就变成了两个子问题
//跳上n-1级台阶的跳法加上n-2级台阶的跳法。

//按照此逻辑递推,跳上n-1级台阶可以拆解为两个子问题
//既:跳上n-2级台阶的跳法加上n-3级台阶的跳法

//f(n) = f(n-1) + f(n-2);

function jump(n) {
    if(n <= 0) return -1;
    if(n == 1) return 1;
    if(n == 2) return 2;
    return jump(n-1) + jump(n-2);
}

动态规划之变态青蛙跳台阶问题

一个青蛙,一次可以跳1级台阶、2级台阶、或n级台阶。

问:这个青蛙跳上n级台阶有多少种跳法

//fn(n) = f(n-1) + f(n-2) + f(n-3) + ... + f(2) + f(1) + f(0);

function jump(n) {
	if(n <= 0) return -1;
    if(n == 1) return 1;
    if(n == 2) return 2;
    var result = 0;
    for(var i = 1 ; i < n ; i ++) {
        result += jump(n-i);
    }
    return result + 1;//+1代表从0级台阶直接跳上去的情况
}

console.log(jump(3));