常用数据结构和算法

数据结构和算法

数组

优点

便于存取

缺点

  1. 删除消耗性能
  2. 占用一段连续的存储空间

链表

优点

  1. 删除效率高
  2. 不占用连续的存储空间

缺点

查询效率低

创建一个链表

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

const list = new Node(0);

let temp = list;
for (let i = 0; i < 8; i++) {
    temp.next = new Node(i + 1);
    temp = temp.next;
}

遍历链表

普通while循环遍历
function traversal(root) {
    while (true) {
        if (root) {
            console.log(root.value);
        } else {
            break;
        }
        root = root.next
    }
}
递归遍历链表
function traversalRecursion(root) {
    if (root === null) {
        return;
    }
    console.log(root.value);
    traversalRecursion(root.next)
}

链表逆置

function reverseLink(root) {
    if (!root.next.next) {
        root.next.next = root;
        return root.next;
    } else {
        const result = reverseLink(root.next);
        root.next.next = root;
        root.next = null;
        return result;
    }
}

排序

/**
 * 传入两个参数,比较是否需要交换,flag为1表示升序,flag = 0表示降序
 * @param {number} a 
 * @param {number} b 
 * @param {number} flag 1 }| 0
 */
function compare(a, b, flag = 1) {
    if (flag) {
        return a - b > 0;
    } else {
        return a - b < 0;
    }
}

/**
 * 交换数组链各个位置的数
 * @param {array} arr 
 * @param {number} a 
 * @param {number} b 
 */
function exchange(arr, a, b) {
    const temp = arr[a];
    arr[a] = arr[b];
    arr[b] = temp;
}

冒泡排序

每次将最大或最小的数,通过数组前后两两交换,冒泡到数组的最后一位。
数组顺序越混乱,效率越低。

/**
 * 冒泡排序
 * @param {array} arr 
 */
function sort(arr) {
    for (let i = 0; i < arr.length; i++) {
        for (let j = 0; j < arr.length - i; j++) {
            if (compare(arr[j], arr[j + 1])) {
                exchange(arr, j, j + 1);
            }
        }
    }
}

选择排序

通过找出数组中除去已排好位置中的最大值或最小值,把它放到数组中未排序部分的最前面。

/**
 * 选择排序
 * @param {array} arr 
 */
function sort(arr) {
    for (let i = 0; i < arr.length - 1; i++) {
        for (let j = i + 1; j < arr.length; j++) {
            if (compare(arr[i], arr[j], 0)) {
                exchange(arr, i, j);
                console.log(arr)
            }
        }
    }
}

快速排序

数组顺序越乱,效率越高。

一种简单的实现,消耗性能(创建多个数组)

每次都找一个领导者,大于这个数的都放在left数组中,大于这个数的都放在right数组中,再对left和right重复操作。

function quickSort(arr) {
    if (arr === null || arr.length === 0) {
        return [];
    }
    const 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 = quickSort(left);
    right = quickSort(right);
    left.push(leader);
    return left.concat(right)

}
效率高
/**
 * 快速排序
 * @param {*} arr 数组
 * @param {*} begin 数组开始处理的位置
 * @param {*} end 数组结束处理的位置
 */
function quickSort(arr, begin, end) {
    if (begin >= end - 1) return;
    let left = begin;
    let right = end;
    do {
        // 找出连续的两段数组,左边一段比领导者,右边一段比领导者大,领导者为arr[begin]
        do {
            left++;
        } while (left < right && arr[left] < arr[begin]);
        do {
            right--;
        } while (right > left && arr[right] > arr[begin]);
        if (left < right) {
            exchange(arr, left, right);
        }
    } while (left < right);
    const swapPointer = left == right ? right - 1 : right;
    exchange(arr, begin, swapPointer);
    quickSort(arr, begin, swapPointer);
    quickSort(arr, swapPointer + 1, end);
}


先进后出


function Stack() {
    this.arr = [];
    this.pop = function() {
        this.arr.pop();
    };
    this.push = function(item) {
        this.arr.push(item);
    }
}

队列

先进先出

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

双向链表

优点

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

缺点

多耗费一个引用空间,且构建较为麻烦

二叉树

创建一个二叉树

function Root(value) {
    this.value = value;
    this.leftChild = null;
    this.rightChild = null;
}


const a = new Root('a');
const b = new Root('b');
const c = new Root('c');
const d = new Root('d');
const e = new Root('e');
const f = new Root('f');
const g = new Root('g');

a.leftChild = b;
a.rightChild = c;
b.leftChild = d;
b.rightChild = e;
c.leftChild = f;
c.rightChild = g;

二叉树遍历

前序遍历

先根,再左子树点再遍历右子树

// 前序遍历
function frontTraversal(root) {
    if (root === null) {
        return;
    }
    console.log(root.value);
    frontTraversal(root.leftChild);
    frontTraversal(root.rightChild);
}
中序遍历

先左子树,再根节点,最后遍历右子树

// 中序遍历
function middleTraversal(root) {
    if (root === null) {
        return;
    }
    middleTraversal(root.leftChild);
    console.log(root.value);
    middleTraversal(root.rightChild);
}

后序遍历

先左子树,再右子树,最后是根节点

// 后序遍历
function lastTraversal(root) {
    if (root === null) {
        return;
    }
    lastTraversal(root.leftChild);
    lastTraversal(root.rightChild);
    console.log(root.value)
}

还原二叉树

根据前序和中序遍历结果还原二叉树
function Node(value) {
    this.value = value;
    this.leftChild = null;
    this.rightChild = null;
}

// 根据树的中序和前序遍历结果还原二叉树
function frontMiddleToTree(front, middle) {
    if (front === null || middle === null || front.length === 0 || middle.length === 0 || front.length !== middle.length) return;
    const root = new Node(front[0]);
    const rootIndex = middle.indexOf(root.value); //在中序遍历中找到根节点的位置
    const frontLeft = front.slice(1, rootIndex + 1); //在前序遍历中的左子树
    const frontRight = front.slice(rootIndex + 1); //在前序遍历中的右子树
    const middleLeft = middle.slice(0, rootIndex); //在中序遍历中的左子树
    const middleRight = middle.slice(rootIndex + 1); // 在中序遍历中的右子树
    root.leftChild = frontMiddleToTree(frontLeft, middleLeft);
    root.rightChild = frontMiddleToTree(frontRight, middleRight);
    return root;
}
根据后序和中序遍历结果还原二叉树
const middle = ['F', 'C', 'G', 'A', 'D', 'B', 'E'];
const last = ['F', 'G', 'C', 'D', 'E', 'B', 'A'];

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


// 根据树的中序和后序遍历结果还原二叉树
function middleLastToTree(middle, last) {
    if (middle === null || last === null || middle.length === 0 || last.length === 0 || middle.length !== last.length) return;
    const root = new Node(last[last.length - 1]);
    const rootIndex = middle.indexOf(root.value); //找到根节点在中序遍历中的位置
    const lastLeft = last.slice(0, rootIndex);
    const lastRight = last.slice(rootIndex, last.length - 1);
    const middleLeft = middle.slice(0, rootIndex);
    const middleRight = middle.slice(rootIndex + 1);
    root.leftChild = middleLastToTree(middleLeft, lastLeft);
    root.rightChild = middleLastToTree(middleRight, lastRight);
    return root;
}

二叉树的搜索

深度优先搜索

搜索未知,和前序遍历顺序一致

/**
 * 深度优先搜索
 * @param {*} root 二叉树的根节点
 * @param {*} target 要查找的值
 * @returns true | false
 */
function dfs(root, target) {
    console.log(root)
    if (root === null) return false;
    if (root.value === target) return true;
    const left = dfs(root.leftChild, target);
    const right = dfs(root.rightChild, target);
    return left || right;
}
广度优先搜索

搜索局域;

/**
 * 广度优先搜索
 * @param {array} rootList 根节点数组
 * @param {*} target 要查询的值
 * @return true | false
 */
function bfs(rootList, target) {
    if (rootList == null || rootList.length === 0) return false;
    let childrenList = []; //存放子节点
    for (let i = 0; i < rootList.length; i++) {
        if (rootList[i] !== null && rootList[i].value === target) {
            return true;
        } else {
            if (rootList[i].leftChild) {
                childrenList.push(rootList[i].leftChild);
            }
            if (rootList[i].rightChild) {
                childrenList.push(rootList[i].rightChild);
            }

        }
    }
    return bfs(childrenList, target);
}

二叉树的比较

不区分左右子树的比较
/**
 * 在区分左右子树的情况下,比较两个树是否相等
 * @param {*} root1 根节点1
 * @param {*} root2 根节点2
 */
function compare(root1, root2) {
    if (root1 === root2) return true;
    if (root1 === null && root2 !== null || root1 !== null && root2 === null) return false;
    if (root1.value !== root2.value) return false;
    return compare(root1.leftChild, root2.leftChild) && compare(root1.rightChild, root2.rightChild)
}
不区分左右子树的比较
/**
 * 在不区分左右子树的情况下,比较两个树是否相等
 * @param {*} root1 根节点1
 * @param {*} root2 根节点2
 */
function compare(root1, root2) {
    if (root1 === root2) return true;
    if (root1 === null && root2 !== null || root1 !== null && root2 === null) return false;
    if (root1.value !== root2.value) return false;
    return (compare(root1.leftChild, root2.leftChild) && compare(root1.rightChild, root2.rightChild)) ||
        (compare(root1.leftChild, root2.rightChild) && compare(root1.rightChild, root2.leftChild))
}

diff算法
/**
 * 找出两棵树不同的地方
 * @param {*} root1 
 * @param {*} root2 
 * @param {array} diffList 存放不同的数组
 */
function diff(root1, root2, diffList) {
    if (root1 === root2) return diffList
    if (root1 === null && root2 !== null) {
        return diffList.push({ type: '新增', origin: null, now: root2 });
    } else if (root1 !== null && root2 === null) {
        return diffList.push({ type: '删除', origin: root1, now: null });
    } else if (root1.value !== root2.value) {
        return diffList.push({ type: '修改', origin: root1, now: root2 });
        diff(root1.leftChild, root2.leftChild, diffList);
        diff(root1.rightChild, root2.rightChild, diffList);
    }
    diff(root1.leftChild, root2.leftChild, diffList);
    diff(root1.rightChild, root2.rightChild, diffList);
}

最小生成树问题

普利姆算法

加点法

  1. 任意找一个点做为起点
  2. 找到以当前选中点为起点路径最短的边
  3. 如果这个边的另一端没有被连通,那么久连接
  4. 如果已经被连通,则继续寻找第二短路径的边
  5. 重复寻找
克鲁斯卡尔算法

加边法

  1. 选择最小的边进行连接
  2. 要保证连接的两端至少有一个点是新的点,或者这个边将两个部落相连
  3. 重复,直到所有点都加入
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值