经典数据结构

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_29918313/article/details/102510462

目录

一、栈

1.【栈】判断出栈是否合法

2.两个栈实现队列

二、链表

1.从尾到头打印链表

2.反转链表

3.单向反转链表

三、二叉树

递归实现

非递归实现

扫描二维码关注公众号,回复: 7628528 查看本文章

中序遍历的前驱后继节点

前驱节点

后继节点

树的深度


一、栈

1.【栈】判断出栈是否合法

var stackA = [1,2,3,4,5]
var stackB = [3,1,2,5,4]
function aa(stackA, stackB) {
  var result = []
  var j = 0
  for (var i = 0; i < stackA.length; i++) {
    result.push(stackA[i])
    while(result.length != 0 && result[result.length - 1] == stackB[j]) {
      result.pop()
      j++
    }
  }
  if (result.length == 0) {
    return true
  } else {
    return false
  }
}
console.log(aa(stackA, stackB))

2.两个栈实现队列

var stackA = []
var stackB = []
function push(node)
{
    stackA.push(node)
}
function pop()
{
    if (stackB.length === 0) {
        if (stackA.length === 0) {
            return null
        } else {
            var len = stackA.length
            for (var i = 0; i < len; i++) {
                stackB.push(stackA.pop())
            }
            return stackB.pop()
        }
    } else {
        return stackB.pop()
    }
}

二、链表

1.从尾到头打印链表

function printListFromTailToHead(head)
{
    
    var res =[], pNode =head;
    while(pNode!=null){
        res.unshift(pNode.val);
        pNode = pNode.next;
    }
    return res
}

2.反转链表

输入一个链表,反转链表后,输出新链表的表头。

function ReverseList(pHead)
{
    var node=pHead, arr=[];
    while(node!=null){
        arr.push(node.val);
        node=node.next;
    }
    node = pHead;
    while(node!=null){
        node.val = arr.pop();
        node = node.next;
    }
    return pHead;
}

3.单向反转链表

var reverseList = function(head) {
    // 判断下变量边界问题
    if (!head || !head.next) return head
    // 初始设置为空,因为第一个节点反转后就是尾部,尾部节点指向 null
    let pre = null
    let current = head
    let next
    // 判断当前节点是否为空
    // 不为空就先获取当前节点的下一节点
    // 然后把当前节点的 next 设为上一个节点
    // 然后把 current 设为下一个节点,pre 设为当前节点
    while(current) {
        next = current.next
        current.next = pre
        pre = current
        current = next
    }
    return pre
};

三、二叉树

1.二叉树的先序,中序,后序遍历

先序遍历表示先访问根节点,然后访问左节点,最后访问右节点。

中序遍历表示先访问左节点,然后访问根节点,最后访问右节点。

后序遍历表示先访问左节点,然后访问右节点,最后访问根节点。

递归实现

递归实现相当简单,代码如下

function TreeNode(val) {
  this.val = val;
  this.left = this.right = null;
}
var traversal = function(root) {
  if (root) {
    // 先序
    console.log(root); 
    traversal(root.left);
    // 中序
    // console.log(root); 
    traversal(root.right);
    // 后序
    // console.log(root);
  }
};

对于递归的实现来说,只需要理解每个节点都会被访问三次就明白为什么这样实现了。

非递归实现

非递归实现使用了栈的结构,通过栈的先进后出模拟递归实现。

以下是先序遍历代码实现

function pre(root) {
  if (root) {
    let stack = [];
    // 先将根节点 push
    stack.push(root);
    // 判断栈中是否为空
    while (stack.length > 0) {
      // 弹出栈顶元素
      root = stack.pop();
      console.log(root);
      // 因为先序遍历是先左后右,栈是先进后出结构
      // 所以先 push 右边再 push 左边
      if (root.right) {
        stack.push(root.right);
      }
      if (root.left) {
        stack.push(root.left);
      }
    }
  }
}

以下是中序遍历代码实现

function mid(root) {
  if (root) {
    let stack = [];
    // 中序遍历是先左再根最后右
    // 所以首先应该先把最左边节点遍历到底依次 push 进栈
    // 当左边没有节点时,就打印栈顶元素,然后寻找右节点
    // 对于最左边的叶节点来说,可以把它看成是两个 null 节点的父节点
    // 左边打印不出东西就把父节点拿出来打印,然后再看右节点
    while (stack.length > 0 || root) {
      if (root) {
        stack.push(root);
        root = root.left;
      } else {
        root = stack.pop();
        console.log(root);
        root = root.right;
      }
    }
  }
}

以下是后序遍历代码实现,该代码使用了两个栈来实现遍历,相比一个栈的遍历来说要容易理解很多

function pos(root) {
  if (root) {
    let stack1 = [];
    let stack2 = [];
    // 后序遍历是先左再右最后根
    // 所以对于一个栈来说,应该先 push 根节点
    // 然后 push 右节点,最后 push 左节点
    stack1.push(root);
    while (stack1.length > 0) {
      root = stack1.pop();
      stack2.push(root);
      if (root.left) {
        stack1.push(root.left);
      }
      if (root.right) {
        stack1.push(root.right);
      }
    }
    while (stack2.length > 0) {
      console.log(s2.pop());
    }
  }
}

中序遍历的前驱后继节点

实现这个算法的前提是节点有一个 parent 的指针指向父节点,根节点指向 null 。

<div align="center"><img src="https://user-gold-cdn.xitu.io/2018/4/24/162f61ad8e8588b7?w=682&h=486&f=png&s=41027" width=400 /></div>

如图所示,该树的中序遍历结果是 4, 2, 5, 1, 6, 3, 7

前驱节点

对于节点 2 来说,他的前驱节点就是 4 ,按照中序遍历原则,可以得出以下结论

  1. 如果选取的节点的左节点不为空,就找该左节点最右的节点。对于节点 1 来说,他有左节点 2 ,那么节点 2 的最右节点就是 5
  2. 如果左节点为空,且目标节点是父节点的右节点,那么前驱节点为父节点。对于节点 5 来说,没有左节点,且是节点 2 的右节点,所以节点 2 是前驱节点
  3. 如果左节点为空,且目标节点是父节点的左节点,向上寻找到第一个是父节点的右节点的节点。对于节点 6 来说,没有左节点,且是节点 3 的左节点,所以向上寻找到节点 1 ,发现节点 3 是节点 1 的右节点,所以节点 1 是节点 6 的前驱节点

以下是算法实现

function predecessor(node) {
  if (!node) return 
  // 结论 1
  if (node.left) {
    return getRight(node.left)
  } else {
    let parent = node.parent
    // 结论 2 3 的判断
    while(parent && parent.right === node) {
      node = parent
      parent = node.parent
    }
    return parent
  }
}
function getRight(node) {
  if (!node) return 
  node = node.right
  while(node) node = node.right
  return node
}

后继节点

对于节点 2 来说,他的后继节点就是 5 ,按照中序遍历原则,可以得出以下结论

  1. 如果有右节点,就找到该右节点的最左节点。对于节点 1 来说,他有右节点 3 ,那么节点 3 的最左节点就是 6
  2. 如果没有右节点,就向上遍历直到找到一个节点是父节点的左节点。对于节点 5 来说,没有右节点,就向上寻找到节点 2 ,该节点是父节点 1 的左节点,所以节点 1 是后继节点

以下是算法实现

function successor(node) {
  if (!node) return 
  // 结论 1
  if (node.right) {
    return getLeft(node.right)
  } else {
    // 结论 2
    let parent = node.parent
    // 判断 parent 为空
    while(parent && parent.left === node) {
      node = parent
      parent = node.parent
    }
    return parent
  }
}
function getLeft(node) {
  if (!node) return 
  node = node.left
  while(node) node = node.left
  return node
}

树的深度

树的最大深度:该题目来自 Leetcode,题目需要求出一颗二叉树的最大深度

以下是算法实现

var maxDepth = function(root) {
    if (!root) return 0 
    return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1
};

对于该递归函数可以这样理解:一旦没有找到节点就会返回 0,每弹出一次递归函数就会加一,树有三层就会得到3。

猜你喜欢

转载自blog.csdn.net/qq_29918313/article/details/102510462
今日推荐