基础数据结构(一):线性结构(数组和栈)

55 阅读4分钟

百度百科给线性结构的定义是:线性结构是一个有序数据元素的集合。在JavaScript这门语言中我们最常见的线性结构就是数组,还有由数组衍生来的队列和栈。队列和栈被称为受限的线性结构,两者可以由数组来实现,但是必须遵守一定的规则:队列必须遵守先进先出原则,而栈则是后进先出原则。还有一种数据结构叫做链表,它也是一种常用的线性结构,但是JavaScript并没有实现这种数据结构,所以我们要自己封装

数组

数组的内存通常是连续的(早期的JavaScript数组是由链表实现的),所以使用下标访问数据的效率非常高,但也由于内存是连续的,他在插入数据时就必须要将插入位置及其以后得数据全部向后移动,如果数组长度不够还需要申请一个更大的连续内存(通常是二倍的长度)来存储数组,此时需要将所有的老数组的内容复制一份到新申请的数组。所以数组的特点就是访问效率高,插入数据效率较低。

栈相信我们还是比较熟悉的,我们在递归调用函数或者嵌套调用函数的时候会发现后调用的函数反而先执行完毕,这里其实就是利用了函数调用栈后进先出的特性。栈结构还有另一个限制,那就是只能从栈顶插入数据,不能像数组那样从中间某个位置或者两端都可以插入数据。 image.png
知道了这样的限制其实我们大概就知道该怎么把一个数组当成栈来使用了,我们只需要严格控制一个数组的插入和删除数据的方式,我们可以把数组的最末端或者最前端当成栈顶(通常会把最尾端当成栈顶,因为这样效率更高),限制只能同时使用push和pop或者只使用unshift和shift方法。(链表结构同样可以实现栈,后续链表内容里会给出具体实现)下面给出一个简单的栈封装(基于数组)

class ArrayStack<T> {
  // 定义一个数组来保存栈元素
  private arr: T[] = [];
  // 入栈
  push(item: T) {
    this.arr.push(item);
  }
  // 出栈
  pop(): T | undefined {
    return this.arr.pop();
  }
  // 返回栈顶元素
  peek(): T | undefined {
    return this.arr[this.arr.length - 1];
  }
  // 判断栈是否为空
  isEmpty(): boolean {
    return this.arr.length === 0;
  }
  // 返回栈的元素个数
  size(): number {
    return this.arr.length;
  }
  // 清空栈
  clear() {
    this.arr = [];
  }
  // 打印栈
  print() {
    if (this.isEmpty()) {
      console.log("Stack is empty");
    } else {
      console.log(this.arr.toString());
    }
  }
}

栈练习

十进制转二进制

这其实是一道数学题,我们怎么把一个十进制数转成二进制? 我们应该这样做

image.png 知道数学上怎么算了那么把这个过程翻译过来其实就是程序的实现了:我们把输入的数字%2的结果入栈,然后把数字除以2向下取整后继续重复上一个步骤直到数字向下取整为零停止,这是再把栈中的数据全部出栈拼接不就是结果了吗,下面给出参考实现,看之前你可以尝试自己先写一下

function decimalToBinary(decNumber: number) {
  const remStack = new ArrayStack<number>();
  let rem: number;
  let binaryString: string = "";

  while (decNumber > 0) {
    rem = Math.floor(decNumber % 2);
    remStack.push(rem);
    decNumber = Math.floor(decNumber / 2);
  }

  while (!remStack.isEmpty()) {
    binaryString += remStack.pop();
  }

  return binaryString;
}

有效的括号

这道题是一道非常经典的利用栈结构解题的leetcode算法题:给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。leetcode.cn/problems/va… 相信你通过上一题对栈结构的理解,对这道题的思路就会非常清晰了:我们肯定需要遍历字符串,对于左半边括号我们直接入栈,如果是右半边括号我们就去栈顶查看是不是匹配的左括号,如果不是那么就不是有效的括号,遍历完字符串后检查栈是否为空,如果不为空则也不是有效的括号。借助这个思路你可以尝试去解一下leetcode这道题。下面给出参考示例:

function isValid(s: string): boolean {
  const stack = new ArrayStack<string>();
  const map = new Map([
    [")", "("],
    ["}", "{"],
    ["]", "["],
  ]);

  for (let i = 0; i < s.length; i++) {
    const c = s[i];
    if (map.has(c)) {
      if (stack.isEmpty() || stack.pop() !== map.get(c)) {
        return false;
      }
    } else {
      stack.push(c);
    }
  }

  return stack.isEmpty();
}

到此相信你已经对栈有了一个初步的认识,对于它的特效和使用场景也有了一个大概的了解