数据结构-1-数组、链表

126 阅读4分钟

数据结构是数据的组织、管理和储存格式,其使用目的是为了高效的访问和修改数据.

其中可以分为物理结构和逻辑结构,

物理结构是在内存中实实在在的储存结构,有:数组、链表等

逻辑结构是抽象的存储结构,依赖于物理结构存在, 有:栈、队列、树、图等

数组

数组是最为简单、最为常见的数据结构.

数组在内存中顺序储存,因此也很好的实现了逻辑上的顺序表

内存是由一个个连续的内存单元组成的,每一个内存单元都有自己的地址,在这些内存单元中,有的被其他数据占用,有的是空闲的.

数组中的每一个元素,都储存在小小的内存单元中,并且元素之间紧密排列,既不能打乱元素的储存顺序,也不能跳过某个储存单元进行储存.

数组的操作

数组读取元素时间复杂度为 O(1)

更新元素时间复杂度为 O(1)

插入(头部插入、中间插入、尾部插入、超范围插入)的时间复杂度为 O(n),数组的扩容时间复杂度为O(n)

删除时间复杂度为O(n)

数组的优劣

数组拥有非常高效的随机访问能力, 只要给出下标,就可以用常量的时间找到对应的元素. 有一种高效查找元素的算法叫做二分查找,就是利用数组的这个优势.

数组的劣势体现在插入和删除元素方面,由于数组元素连续紧密的储存在内存中,插入、删除元素都会导致大量元素被迫移动,影响效率.

总体上数组适合读操作多,写操作少的场景

链表

链表是一种在物理上非连续、非顺序的数据结构,由若干节点(node)组成

单向链表

单向链表的每一个节点又包含两部分,一部分是存放数据的变量data,另一部分是指向下一个节点的指针 next

  interface Node{
      data:any,
      next:Node
  }

链表中的第一个节点被称为头节点,最后一个节点被称为尾节点,尾节点的next指针指向空

与数组按照下标来随机寻找元素不同,对于链表的其中一个节点,只能根据节点A的next指针来找到下一个节点B,再根据节点B的next指针找到下一个节点C.......

双向链表

双向链表具有回溯到它前置节点的能力

双向链表的每一个节点除了拥有 data 和 next 指针, 还拥有指向前置节点的 prev 指针

链表的储存方式

如果说数组在内存中的储存方式是顺序储存, 那么链表在内存中的储存方式则是随机储存

什么叫随机储存, 数组在内存中占用了连续完整的内存空间,而链表则是采用了见缝插针的方式,这样可以灵活有效的利用零散的碎片空间.

图中的箭头代表链表节点的next指针

链表的基本操作

1. 查找

在查找元素时, 链表不像数组那样可以根据下标快速定位,只能从头节点开始,向后一个一个节点逐一查找, 所以时间复杂度为O(n)

2. 更新

尾部插入、头部插入、中间插入的时间复杂度都为O(1)

3. 删除

尾部删除、头部删除、中间删除的时间复杂度都为O(1)

实现代码

    interface LinkedNodeType{
        data: any;
        next: LinkedNodeType | null;
    }
    
    
    class LinkedNode{
        data: any
        next: LinkedNodeType | null
        public constructor(data: any){
            this.data = data;
            this.next = null;
        }
    }
    
    class LinkedData{
        size: number;
        head: LinkedNodeType|null;
        last: LinkedNodeType|null;
        constructor(){
            this.size = 0; // 链表实际长度
            this.head = null; // 头节点指针
            this.last = null; // 尾节点指针
        }
    
    
        /**
         * @description 插入元素操作
         * @param {*} data  插入的元素
         * @param {*} index 插入的位置
         */
        insert(data:any, index:number): void{
            if(index < 0 || index > this.size) {
                throw new Error('超出链表节点范围');
            }
            const node = new LinkedNode(data);
            if(this.size === 0) {
                // 插入头部
                this.head = node;
                this.last = node;
            } else if(this.size === index){
                // 插入尾部
                (this.last as LinkedNode).next = node;
                this.last = node;
            } else {
                // 插入中间
                const prevNode = this.getNode(index - 1);
                const nextNode = this.getNode(index + 1);
                prevNode.next = node;
                nextNode.next = nextNode;
            }
            this.size++;
        }
        /**
         *
         * @description 查找元素操作
         * @param {number} index
         * @memberof LinkedData
         * @returns 返回查找的节点
         */
        getNode(index:number): LinkedNodeType {
            if(index < 0 || index >= this.size) {
                throw new Error('超出链表节点范围');
            }
            let temp = (this.head as LinkedNode);
            for (let i = 0; i < index; i++) {
                temp = (temp.next as LinkedNode);
            }
            return temp;
        }
    
    
        /**
         * @description 删除节点
         * @param {*} index
         * @memberof LinkedData
         */
        remove(index:number): LinkedNodeType {
            if(index < 0 || index >= this.size) {
                throw new Error('超出链表节点范围');
            }
            let removeNode = null;
            if(index === 0) {
                // 删除头部节点
                removeNode = this.head;
                this.head = (this.head as LinkedNode).next;
            } else if(index === this.size - 1) {
                // 删除尾部节点
                const preNode = this.getNode(index - 1);
                removeNode = preNode.next;
                preNode.next = null;
                this.last = preNode;
            } else {
                // 删除中间节点
                const prevNode = this.getNode(index - 1);
                const nextNode = (prevNode.next as LinkedNode).next;
                removeNode = prevNode.next;
                prevNode.next = nextNode;
            }
            this.size--;
            return removeNode as LinkedNode;
        }
        /**
         * @description 输出链表
         * @memberof LinkedData
         */
        outPut(): void{
            let temp = this.head;
            while(temp !== null){
                console.log(temp.data)
                temp = temp.next;
            }
        }
    }
    
    
    const link = new LinkedData();
    link.insert('hello', 0);
    link.insert('hi', 1);
    link.insert('第三个', 2);
    link.insert('第四个', 3);
    link.remove(0);
    link.outPut();

数组VS链表

数组和链表各有千秋:

数组更适合读多 写少的操作,链表更适合读少,写多的操作

摘要总结自: 漫画算法 小灰的算法之旅