前端算法笔记

这篇博客详细介绍了前端开发中常见的数据结构,如数组、链表、栈、队列、双向链表和二维数据结构(树形结构)。博主探讨了各种数据结构的特点、优缺点,以及在JavaScript中的实现。此外,还深入讲解了二叉树的遍历方法和图的两种算法——普利姆算法和克鲁斯卡尔算法,强调了它们在实际问题中的应用。
摘要由CSDN通过智能技术生成

1.数组

1.线性数据结构强调存储与顺序
2.有东西先把东西存起来


数组特点

数组定长

数组是定长的,我们以为的其实是js引擎帮我们扩容,内存是操作系统来分配的
尽量给自己的东西设置好数组长度

特性

1.存储在物理空间上(在磁盘上)是连续的。如果存储的到头了,就再去磁盘里找,有没有一个空间能存放的下它。
在这里插入图片描述
2.底层数组的长度不变。(就是你一开始设置了是多长,就是多长)
3.数组变量指向第一个元素的位置(数组下标)

数组优点

1.查询性能好(通过偏移查询)

数组缺点

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

在js中创建一个数组

let a = [1,2,3]
let arr = new Array(10) //确定数组长度

2.链表

在这里插入图片描述
数组的存放是连续的,链表的存放是可以碎片的,用引用来把他们连起来。

链表特点

1.空间上不连续
2.每存放一个值都会有一个引用空间

优点

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

缺点

1.链表的查询速度慢
2.链表的每一个查询都需要创建一个next值(就是引用值),直接double了空间。但是可以通过增加数据量的存储来降低引用值大小(引用值大小没变,只是相对于数据量变小了)

延伸(重要):

1.想传递一个链表,必须传递链表的根节点
2.每一个链表的节点都可以认为自己是根节点

数组和链表

循环遍历方法

数组

// 遍历数组  经典for循环
let a = [1, 2, 3, 4, 5]

function travel(array) {
    if (array == null) return; // 一定要做这个检验
    for (let index = 0; index < array.length; index++) {
        console.log(array[index]);
    }
}
travel(a)

链表

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

let a = new ode(1);
let b = new ode(2);
let c = new ode(3);
let d = new ode(4);

a.next = b;
b.next = c;
c.next = d;

// 循环遍历  传递一个链表 传的是根节点
function travel(root) {
    let temp = root;
    while (true) {
        if (temp == null) {
            break;
        } else {
            console.log(temp.value);
        }
        temp=temp.next;
    }
}
travel(a)

递归遍历方法

数组

let a = [1, 2, 3, 4, 5]
function travel(arr, i) {
    if (arr == null || i >= arr.length) return;
    console.log(arr[i], '递归遍历');
    travel(arr, i + 1)
}
travel(a, 0)

链表

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

let a = new ode(1);
let b = new ode(2);
let c = new ode(3);
let d = new ode(4);

a.next = b;
b.next = c;
c.next = d;

function travel(root) {
    if (root == null) return;
    console.log(root.value, '递归算法');
    travel(root.next) // 找到一直在循环做的事情
}
travel(a)

链表逆置

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

let a = new ode(1);
let b = new ode(2);
let c = new ode(3);
let d = new ode(4);

a.next = b;
b.next = c;
c.next = d;

function reverse(root) {
    //定位到倒数第二个节点,
    if (root.next.next == null) {
        //把最后一个节点指向倒数第二个
        root.next.next = root;
        //退出节点
        return root.next;
    } else {
        //递归
        let result = reverse(root.next);
        //让该节点的下一个节点指向自己
        root.next.next = root;
        //让该节点指向null
        root.next = null;
        return result;
    }
}
//遍历一下
function travel(root) {
    if (root == null) return;
    console.log(root.value);
    travel(root.next)
}
travel(reverse(a))

冒泡排序(比较、交换、排序)

  • 算法步骤
    • 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
    • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
    • 针对所有的元素重复以上的步骤,除了最后一个。
    • 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
let arry = [4, 7, 2, 6, 0, 8, 9, 1, 3];
/**
 - 比较
 - 交换
 - 排序
 - */
function compare(a, b) {
    if (a > b) return true;
    else return false;
}
function exchange(arry, i, j) {
    let temp = arry[j]
    arry[j] = arry[i]
    arry[i] = temp
}
function sort(arry) {
    for (let i = 0; i < arry.length; i++) {
        for (let j = 0; j < arry.length - 1 - i; j++) {
            if (compare(arry[j], arry[j + 1])) {
                exchange(arry, j, j + 1)
            }
        }
    }
    return arry;
}
console.log(sort(arry));

选择排序

  • 算法步骤
    • 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
    • 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
    • 重复第二步,直到所有元素均排序完毕。
let arry = [4, 7, 2, 6, 0, 8, 9, 1, 3];

function compare(a, b) {
    if (a > b) return true;
    else return false;
}
function exchange(arry, i, j) {
    let temp = arry[j];
    arry[j] = arry[i];
    arry[i] = temp;
}
function sort(arry) {
    for (let i = 0; i < arry.length; i++) {
        let maxIndex = 0;
        for (let j = 0; j < arry.length - i; j++) {
            if (compare(arry[j], arry[maxIndex])) {
                maxIndex = j;
            }
            exchange(arry, maxIndex, arry.length - 1 - i);
        }
    }
    return arry;
}
console.log(sort(arry));

简单快速排序

  • 算法步骤
    • 从数列中挑出一个元素,称为 “基准”
    • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
    • 递归地把小于基准值元素的子数列和大于基准值元素的子数列排序;
      在这里插入图片描述
let arr = [4, 7, 2, 6, 0, 8, 9, 1, 3];
function quicklySort(arr) {
    if (arr.length == 0 || arr == null) return [];
    let leader = arr[0];
    let left = [];
    let right = [];
    for (let i = 1; i < arr.length; i++) {
        if (arr[i] < leader) {
            left.push(arr[i])
        } else {
            right.push(arr[i])
        }
    }
    // 递归 
    left = quicklySort(left);
    right = quicklySort(right);
    // 把 left leader right 的内容给拼接起来
    left.push(leader)
    let newArry = left.concat(right)
    return newArry

}
console.log(quicklySort(arr));

标准快速排序

let arr = [4, 7, 2, 6, 0, 8, 9, 1, 3];
//交换
function exchange(arr, a, b) {
    let temp = arr[a];
    arr[a] = arr[b];
    arr[b] = temp;
}
// 值 ,左指针,右指针
function quicklySort(arr, begin, end) {
    //数组的长度小于两个的时候就跳出
    if (begin > end - 1) return;
    let left = begin;
    let right = end;
    do {
        do {
            left++;
        } while (left < right && arr[begin] > arr[left]);
        do {
            right--;
        } while (left < right && arr[begin] < arr[right]);
        if (left < right) exchange(arr, left, right);
    } while (left < right);

    let exchangePoint = left == right ? right - 1 : right;
    exchange(arr, exchangePoint, begin);
    quicklySort(arr, begin, exchangePoint);
    quicklySort(arr, exchangePoint + 1, end)
}
function sort(arr) {
    //左闭右开区间
    quicklySort(arr, 0, arr.length);
}
sort(arr)
console.log(arr);

另一种方法 用到了 slice filter

let arr = [4, 7, 2, 6, 0, 8, 9, 1, 3];
/**
 * 定义arr是一个number值的数组
 * @param {number[]} arr 
 */
function quicklySort(arr) {
    if (arr.length < 2) {
        return arr
    }
    let goal = arr[0]
    //从数组的第一项开始,过滤出小于目标值的项
    let minArr = arr.slice(1).filter(item => item <= goal)
    //从数组的第一项开始,过滤出大于目标值的项
    let maxArr = arr.slice(1).filter(item => item >= goal)
    //递归 拼接
    return quicklySort(minArr).concat([goal]).concat(quicklySort(maxArr))
}
console.log(quicklySort(arr));

3.栈

pop() 方法

用于删除数组的最后一个元素并返回删除的元素。

栈只有一个口
在这里插入图片描述
演示的代码

// 相当于 桶 
let arr = [];
//进桶
function enter(arr, value) {
    arr.push(value);
}
//出桶
function out(arr) {
    arr.pop();
}
enter(arr,1)
console.log(arr);
enter(arr,2)
console.log(arr);
enter(arr,3)
console.log(arr);
out(arr)
console.log(arr);
out(arr)
console.log(arr);

输出:
在这里插入图片描述
简单封装一下代码:

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

在这里插入图片描述

4.队列

shift() 方法

用于把数组的第一个元素从其中删除,并返回第一个元素的值。

队列一个口进一个口出
在这里插入图片描述

function Queue() {
    this.arr = [];
    this.enter = function (value) {
       return this.arr.push(value);
    };
    this.out = function () {
        return this.arr.shift();
    } ;
}
let queue = new Queue();
queue.enter(1)
console.log(queue.arr);
queue.enter(2)
console.log(queue.arr);
queue.enter(3)
console.log(queue.arr);
queue.out()
console.log(queue.arr);
queue.out()
console.log(queue.arr);
queue.out()
console.log(queue.arr);

输出
在这里插入图片描述

5.双向链表

在这里插入图片描述

function Node(value) {
    this.value = value;
    this.next = null;
    this.pre = null;
}
let node1 = new Node(1)
let node2 = new Node(2)
let node3 = new Node(3) 

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

优缺点

优点:能轻松的遍历整条链表
缺点:耗费存储空间

6.二维数据结构

树形结构(有向无环图)

特点

  • 树形结构只有一个根节点
  • 树形结构没有回路
  • 叶子节点
    - 下面没有根节点
  • 节点
    - 普通几点(不是根节点和叶子节点)
  • 树的度
    -各个节点叉最多的,有多少叉
  • 树的深度
    - 树中结点的最大层次数

满二叉树

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

在这里插入图片描述

完全二叉树

  • 国内定义:
    1.叶子节点都在最后一或倒数第二层
    2.叶子节点向左靠拢
  • 国际定义:

1.叶子节点都在最后一或倒数第二层
2.如果有叶子节点,必然有两个叶子节点
在这里插入图片描述

遍历二叉树

在这里插入图片描述

前序遍历(先根次序遍历) 当前 左边 右边 ABDECFG
// 前序遍历(先根次序遍历) 当前 左边 右边 A BDE CFG

function Node(value) {
    this.value = value;
    this.left = null;
    this.right = null;
}
// 给每个节点赋值 
let a = new Node("a");
let b = new Node("b");
let c = new Node("c");
let d = new Node("d");
let e = new Node("e");
let f = new Node("f");
let g = new Node("g");

//写左边邻居 右边邻居 A的左是B A的右是C
a.left = b;
a.right = c;

b.left = d;
b.right = e;

c.left = f;
c.right = g;

//遍历链表 树 之类的 要传递一个根节点进来 这里的根节点是a
function fl(root) {
    if (root === null) return;
    console.log(root.value);
    // 小递归 
    fl(root.left)
    fl(root.right)
}
fl(a);

打印结果
在这里插入图片描述

中序遍历(中根次序遍历) 左边 当前 右边 DBEAFCG
// 中序遍历(中根次序遍历) 左边 当前 右边 DBE A FCG
function Node(value) {
    this.value = value;
    this.left = null;
    this.right = null;
}

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

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


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

运行结果
在这里插入图片描述

后序遍历(后根次序遍历) 左边 右边 当前 DEBFGCA
function Node(value) {
    this.value = value;
    this.left = null;
    this.right = null;
}

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

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

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

运行结果
在这里插入图片描述

还原二叉树

已知前序遍历和中序遍历还原二叉树 ,进行后序遍历
let forward = ["A", "B", "D", "E", "C", "F", "G",]
let middle = ["D", "B", "E", "A", "F", "C", "G",]

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

function f1(forward, middle) {
    //严谨性判断
    if (forward == null || middle == null || forward.length !== middle.length || forward.length == 0 || middle.length == 0) return null;

    //前序遍历的第一个值就是根节点
    let root = new Node(forward[0]);
    //先找到 根节点 在middle里排第几位 3
    let index = middle.indexOf(root.value);
    //slice取的是左闭右开区间 
    //中序遍历的  左边 就是 在index 之前的  从下标第0个开始 到index个(不取index)
    let middleLeft = middle.slice(0, index);
    //中序遍历的  右边 就是 从index之后的 
    let middleRight = middle.slice(index + 1, middle.length)
    //前序遍历的 左边 就是 从 index 之后的index+1个 因为index下标为0,所以从1开始
    let forwardLeft = forward.slice(1, index + 1);
    //前序遍历的 右边 就是 从 index+1个之后 
    let forwardRight = forward.slice(index + 1, forward.length)
    //递归 先前序后中序
    root.left = f1(forwardLeft, middleLeft)
    root.right = f1(forwardRight, middleRight)
    return root;
}
//后续遍历
function f2(root) {
    if (root == null) return;
    f2(root.left)
    f2(root.right)
    console.log(root.value);
}
f2(f1(forward, middle))

结果:
在这里插入图片描述

已知中序遍历和后序遍历还原二叉树 ,进行前序遍历

let middle = ["D", "B", "E", "A", "F", "C", "G",]
let back = ["D", "E", "B", "F", "G", "C", "A",]

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

function f1(middle, back) {
    //严谨性判断
    if (middle == null || back == null || back.length !== middle.length || back.length == 0 || middle.length == 0) return null;

    //后续遍历的最后一个就是A
    let root = new Node(back[back.length-1]);
    //先找到 根节点 在middle里排第几位 3
    let index = middle.indexOf(root.value);
    //slice取的是左闭右开区间 

    //中序遍历的  左边 就是 在index 之前的 
    let middleLeft = middle.slice(0, index);
    //中序遍历的  右边 就是 从index之后的
    let middleRight = middle.slice(index + 1, middle.length)

    //后序遍历的 左边 就是 从 1 之后的index+1个
    let backLeft = back.slice(0, index );
    //后序遍历的 右边 就是 从 index+1个之后 
    let backRight = back.slice(index , back.length-1)
    //递归 
    root.left = f1(middleLeft, backLeft)
    root.right = f1(middleRight, backRight)
    return root;
}
function f2(root) {
    if (root == null) return;
    console.log(root.value);
    f2(root.left)
    f2(root.right)
}
f2(f1(middle,back))

运行结果
在这里插入图片描述

二叉树的深度优先搜索

优点:实现比较简单
缺点:容易爆栈,如果很多的话
在这里插入图片描述

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

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

a.left = b;
a.right = c;

b.left = d;
b.right = e;

c.left = f;
c.right = g;

function deepSearch(root,target){
    if(root == null) return false;
    if(root.value == target) return true;
    let left = deepSearch(root.left,target);
    let right = deepSearch(root.right,target);
    return left || right
}
console.log(deepSearch(a,"g"));

假如说我搜索一个g,g在二叉树里面,所以结果是true
在这里插入图片描述

二叉树的广度优先搜索

实现方法:先拿个数组,把A装进去,如果没有,把A移除,装B,C,如果没有,把BC移除,装DEFG,如果找到了,返回true。
在这里插入图片描述

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

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

a.left = b;
a.right = c;

b.left = d;
b.right = e;

c.left = f;
c.right = g;

//广度优先搜索
function wideSearch(rootList,target){
    if(rootList == null && rootList.length == 0) return false;
    let childList = [];
    for(let i = 0;i<rootList.length;i++){
        if(rootList[i] !== null && rootList[i].value == target){
            return true
        }else{
            childList.push(rootList[i].left)
            childList.push(rootList[i].right)
        }
    }
    //如果最后一位为空的话,就返回false
    if(childList[childList.length-1]==null){
        return false
    }else{
        return wideSearch(childList,target)
    } 
}
console.log(wideSearch([a],"h"));

假如说我搜索一个h,h不在二叉树里面,返回false
在这里插入图片描述

二叉树的比较

默认左右子树不分次序

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

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

a1.left = b1;
a1.right = c1;
b1.left = d1;
b1.right = e1;
c1.left = f1;
c1.right = g1;

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

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

function compareTree(root1,root2){
    //都为空的话肯定是相同的
    if(root1 == null && root2 == null) return true
    //一个为空 另一个不为空的话,结构都不一样,肯定是不相同的
    if(root1 == null && root2 !== null ||root1 !== null && root2 == null) return false
    //value值不相等,两个二叉树不相同
    if(root1.value!==root2.value) return false
    let leftBool =  compareTree(root1.left,root2.left)
    let rightBool =  compareTree(root2.right,root2.right)
    return leftBool && rightBool
}
console.log(compareTree(a1,a2));

二叉树左右子树互换后的比较

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

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

a1.left = b1;
a1.right = c1;
b1.left = d1;
b1.right = e1;
c1.left = f1;
c1.right = g1;

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

a2.left = c2; //互换了c2,b2
a2.right = b2; //互换了c2,b2
b2.left = d2;
b2.right = e2;
c2.left = f2;
c2.right = g2;

function compareTree(root1, root2) {
    if (root1 == null && root2 == null) return true
    if (root1 == null && root2 !== null || root1 !== null && root2 == null) return false
    if (root1.value !== root2.value) return false
    return compareTree(root1.left, root2.left) && compareTree(root1.right, root2.right) || compareTree(root1.left, root2.right) && compareTree(root1.right, root2.left)
}
console.log(compareTree(a1, a2));

结果为true

二叉树的diff算法

输出二叉树不一样的地方
增加了什么,改动了什么,删除了什么

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

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

a1.left = b1;
a1.right = c1;
b1.left = d1;
b1.right = e1;
c1.left = f1;
c1.right = g1;

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

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


// let diffList=[
//     {
//         type:"增加了||删除了||改动了",
//         origin:c1, //原始
//         now: ff , //现在
//     }
// ];
let diffList = [];
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.value,
            now:root2.value,
        })
    }else{
        diffTree(root1.left,root2.left,diffList);
        diffTree(root1.right,root2.right,diffList);
    }
}
diffTree(a1,a2,diffList);
console.log(diffList);

7.图

普利姆算法(加点法)

1.挑选出一个节点
2.挑选出一个最短的边区进行联通
3.如果对方没有被联通进系统里则进行联通
4.如果被连进了系统里则去寻找第二短的边
5.重复2-4步

克鲁斯卡尔算法

1.先找最小的边
2.如果说链接的两个点有为两个系统的或者至少有一个没有被链接
3.则加边
4.重复以上三部

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值