banner
SlhwSR

SlhwSR

热爱技术的一名全栈开发者
github
bilibili

重新认识数据结构

1. 概念#

数据结构与算法是计算机科学的重要基础知识。
数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。它涉及数据的存储、组织和管理。常见的数据结构包括数组、链表、栈、队列、树、图等。
算法是完成特定任务的步骤或指令的序列。它本质上是对数据操作的一种描述。算法主要包含输入、输出以及转换输入为输出的步骤。
数据结构与算法之间有着密不可分的关系。算法的设计取决于所选的数据结构,不同的数据结构适用于不同的算法。而数据结构的组织方式直接影响算法的效率。
掌握数据结构与算法对开发高质量程序非常重要。它们是评估开发人员技术能力的重要标准。数据结构与算法知识也是面试中的常见考点。许多编程语言都需要实现和使用各种数据结构与算法。
所以数据结构与算法是每个程序员必备的基础知识,需要通过大量的学习、练习和使用才能真正掌握。它们是开发人员必须重点提高的核心竞争力之一。

数据结构构成#

1. 栈结构
2. 队列
3. 数组
4. 链表
5. 树
6. 图
7. 堆结构

1.1 栈结构的实现#

实现方法:基于数组和基于链表,由于 ts/js 中不存在链表,需要重新实现。

interface IStack<T>{
  push():void;
  pop():void;
  size():number
  peek():T
  isEmpty():bollean
}

class Stack<T=any> immplements IStack{
  private arr:T[]=[]
  push(element:T):void{
     this.arr.push(element)
  }
  pop():void{
     this.arr.pop()
  }
  size():number{
    return this.arr.length();
  }
  peek():T|undefined{
    return this.arr.pop()
  }
  isEmpty():bollean{
    return this.arr.length===0?true:false
  }
}

面试题 1. 实现一个十进制转二进制的函数#

function toBinary(count:number):string{
  let stack =new Stack()
  let binary=""
  while(count>0){
    stack.push(count%2)   
    count= Math.floor((count/2))
  }
  while(!stack.isEmpty()){
    binary+=stack.pop()
  }
  return binary;
}

面试题 2. 实现字符 “(”,“{”,“[” 与 "}","]",#

")" 对称
例如: valid ("(){}[]") true
valid("({[]})") true
valid("{()]}") false

function valid(str:string):boolean{
  const stack=new Stack()
  for(let i=0;i<str.length;i++){
    switch(str[i]){
      case "(":
        stack.push(")")
        break;
      case "{":
        stack.push("}")
        break;
      case "[";
        stack.push("]")
        break;
      default:
         if(str[i]!==stack.pop()) return false
        
    }
  }
  return stack.isEmpty()
}

击鼓传花#

1. 一群人围着圈,开始数,当停止时,数数该人被淘汰,并得出最后一个胜利者的初始位置。

思路:根据队列依次出栈,若不为叫到人,则再进栈。#

function findLast(arr:string[],loop:number){
  if (arr.length === 0) return -1;
  const queue=new Queue()
  for(let i=0;i<arr.length;i++){
     queue.enqueue(arr[i])
  }
  while(queue.size()>1){
  for(let i=1;i<loop;i++){
    const name=queue.dequeue()
    queue.enqueue(name)
  }
  queue.dequeue()
  }
  return queue.dequeue()
}

链表结构#

概念:和数组一样是线性结构可用于存储一系列元素,但列表和数组的实现机制完全不同,在什么场景下用到数组或链表,取决于应用场景的操作。

数组:在空间内分配一块固定大小的连续空间用于存贮数据,但当在数组前、中插入元素时,所有的之后的元素都要向后移动响应的内存地址,如果数组元素特别多的情况下,开销会很大。

链表:链表不需要在创建时就确定大小,根据 head 指针指向链表中 next 的元素,head=>next=>next2=>next3=>next4
当设计到大量删除、插入操作时,只需要改变 next 的指针指向即可,时间复杂度为 O (1) ,但他的缺点很明显,数组通过索引即可寻址,但链表需要走头开始。

链表的实现#

class Node<T=any>{
  value:T;
  next:Node<T>|null
  constructor(value:T){
    this.value=value
  }
}
class LinkList<T>{
  head:T|null=null;
  size:number=0;
  get length():number{
    return this.size
  }
  add(element):void{
    const newNode=new Node<T>(element)
    if(!this.head){
      this.head=newNode
    }else{
      let current=this.head
      while(current.next){
        current=current.next
      }
      current.next=newNode
    }
    this.size++
  }
  traverse(){
   let values:T[]=[]
   let current=this.head
   while(current){
      values.push(current.value)
      current=current.next
   }
    console.log(values.join("->"))
  }
  insert(elememt:T,position:number):boolean{
    if(position<0||position>this.size) return false
    const value=new Node(element)
    if(position===0){
      value.next=this.head
      this.head=value
    }else{
      let prev:Node<T>|null=null
      let current=this.head
      let index=0
      while(index++<position){
        prev= current;
        current=current.next
      }
      prev.next=value
      value.next=current
    }
    this.size++
    return true
  }
  //删除某个位置节点
  removeAt(position:number):T|null{
    if(position<0||position>=this.size)
      return null
    if(position===0){
      this.head=this.head.next
    }else{
      let prev:Node<T>|null=null
      let current =this.head
      let index=0
      while(index++<position){
        prev=current
        current=current.next
      }
      prev.next=current?.next ?? null
    }
    this.size--;
    return null;
  }
  //获取某个位置的节点
  get(position:number):T|null{
     if(position>size||position<0) return null
    let current=this.head
    let index=0
    while(index++<position){
      current=current.next ?? null
    }
    return current.value??null
  }
  //更新某个节点的元素
  update(element:T,position:number):boolean{
    if(position>=size||position<0) return false
    let current=this.head
    let index=0
    while(index++<position&&current){
       current=current.next
    }
    current.value=element
    return true;
  }
  //根据元素寻找下标
  indexOf(element:T){
    let current=this.head
    let index=0;
    while(current){
      if(current.value===element){
        return index;
      }
      current=current.next
      index++
    }
    return -1;
  }
   remove(element: T): T | null {
    const first = this.indexOf(element);
    //方法一:
    // if (first > 0) {
    //   let prev: Node<T> | null = null;
    //   let current = this.head;
    //   let index = 0;
    //   while (index++ < first && current) {
    //     prev = current;
    //     current = current.next;
    //   }
    //   prev!.next = current!.next ?? null;
    //   return true;
    // } else {
    //   return false;
    // }
    return this.removeAt(first);
  }
  isEmpty(): boolean {
    return this.size === 0 ? true : false;
  }
}

面试题反转链表 (栈结构实现)#

class ListNode {
  val: number;
  next: ListNode | null;
  constructor(val?: number, next?: ListNode | null) {
    this.val = val === undefined ? 0 : val;
    this.next = next === undefined ? null : next;
  }
}
function reverseList(head: ListNode | null): ListNode | null {
  if (head === null) return null;
  //只有一个节点
  if (!head.next) return head;
  //使用栈结构
  let stack: ListNode[] = [];
  while (head) {
    stack.push(head);
    head = head.next;
  }
  const newhead: ListNode = stack.pop()!;
  let newheadCurrent = newhead;
  while (stack.length) {
    const node = stack.pop();
    newheadCurrent.next = node!;
    newheadCurrent = newheadCurrent!.next;
  }
  newheadCurrent.next = null; //因为最后一个元素指向null,而不是前一个,否则会循环引用
  return newhead;
}
export {};

反转链表(非递归实现)#

class ListNode {
  val: number;
  next: ListNode | null;
  constructor(val?: number, next?: ListNode | null) {
    this.val = val === undefined ? 0 : val;
    this.next = next === undefined ? null : next;
  }
}
function reverseList(head: ListNode | null): ListNode | null {
 if(head===null ||!head.next) return head
 let newHead:ListNode|null=null
  while(head){
    
    let current:ListNode |null=head.next
    //让第一个节点指向null,
    head.next=newHead
    //向后推进,依次类推
    newHead=head
    head=current
  }
  return newHead;
}

反转链表(递归)#

class ListNode {
  val: number;
  next: ListNode | null;
  constructor(val?: number, next?: ListNode | null) {
    this.val = val === undefined ? 0 : val;
    this.next = next === undefined ? null : next;
  }
}
function reverseList(head: ListNode | null): ListNode | null {
  if(head===null || head.next===null) return head
  const newhead=reverseList(head.next??null)
  head.next.next=head
  head.next=null
  return newhead
}

哈希表#

哈希表是基于数组实现的,但是区别于数组有许多优势:

优点#

1. 提供非常快速的查找、删除、插入操作
2. 无论多少数据,插入、删除的时间都接近常量 O (1),实际上只需要几行机械指令即可。
3. 哈希表的速度比树还要快,基本可以瞬间查到目标元素。
4. 哈希表比树结构更容易编写。

缺点#

1. 哈希表的数据结构是无序的,所以没有一种固定的方式来遍历元素。
2. 通常情况下,哈希表的 key 不能重复,不能放置相同的 key, 用于保存不同的元素。

本质#

它的结构就是数组结构,但是它的神奇之处在于数组下标值的变换,这种变换我们可以使用哈希函数,通过哈希函数获取 hashCode。

比如存贮 50000 本图书信息,数组:复杂度 o (n),顺序查找下标值索引,当然,可以利用二分将复杂度降到 O (logn),但是利用哈希函数,可以提前将每一项图书的每一项获取到对应的下标即索引值,这个函数规则可以由我们自己来制定,
比如 每本书对应存贮字符串 "abc",按照 ascii 编码得到一个规则 下标 = 97+98+99,=== 该数组的下标值。

显而易见,这种存储方式的重复率极高,因此方案 b 可以解决这个问题 幂的连乘
比如 “abc"=271^3+272^3......
但是这样做,还有弊端,虽然保持了唯一性,但是极其浪费数组空间,比如 “zzzzzzzzzzzzzzzzzzz”, 需要分配天文数字的存贮空间,这是不科学的。

因此 链地址法开放地址法

  1. 对关键字 key 计算哈希函数,获得散列地址。
  2. 查看该散列地址,如果为空,则直接插入。
  3. 如果不为空,则检查该地址上的链表中是否存在相同 key 的元素。
  4. 如果存在,则修改内容。如果不存在,则在该链表头部插入一个新的元素。
  5. 插入新元素时,需要创建结点,并将新结点插入链表头部。
  6. 查找元素时,需要遍历链表,逐一比对关键字。
    链地址法解决了冲突,但查找效率较低,需要遍历链表。平均查找长度约为 O (1+α),α 是链表长度。
加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。