算法基础篇-链表

在上一篇文章中,我们说到了栈与队列,这章中,我们来看看另一个基础的知识,链表

链表

链表是存储的有序元素的集合,但是不同于数组连续存储方式,链表在存储中并不是采用连续放置的方式,而是每一个元素由节点和指向下一个元素的引用(其他语言中的指针)。所以我们可以得知链表的具体的形式应该是![链表]
image.png

那么我们将上图进行拆分,我们可以很明确的得到一个链表应该有的数据结构,如下图:
image.png

从上图可以得知,一个链表我们可以将其拆分List类,和node类。所以我们可以先实现这两个类
Node类
/**
 * @export
 * @class LinkNode
 */
export default class LinkNode {
    [x: string]: undefined;
    public node:any;
    public next: undefined;
    constructor(node:any) {
        this.node = node;
        this.next = undefined;
    }
}
List类
 *
 * @export
 * @class LinkList
 */
import LinkNode from './LinkNode'
export default class LinkList {
    public count: number;
    public head: any | undefined;
    constructor() {
        this.count = 0;
        this.head = undefined;
    }
}

那么一个链表应该需要哪些方法呢?从个人理解中,同队列和栈一样需要以下方法

方法名 描述
push(item) 添加一个元素到链表中
insert(item,index) 添加一个元素到链表指定位置中
getItemAt(index) 获取链表指定位置元素
remove() 移除最后一项
clear() 清除链表
size() 返回链表大小
removeAt(index) 移除指定位置元素并返回
indexOf(item) 获取指定元素所在的位置

下面我们来一一分析上面方法的具体实现

Push

向链尾添加元素。那么有两种情况,一种是最初始状态下添加,根据我们前面的list类,我们可以看出,当前数据格式为,也就是一个head指向的是undefined。那么我们添加一个数据后,我们的数据格式应该为什么样呢,第二种情况为在已有的数据格式的基础下添加,那么我们可以思考下怎么实现呢?
我们来分析下,也就是当前最后一个变成了倒数第二个,push进来的node为最后一个,所以只需要将当前最后一个的next指向当前传入的即可。如图


push方法分析

因此Push方法就此实现

 /**
     *尾部插入元素
     *
     * @param {*} item
     * @memberof LinkList
     */
    public push(item: any) {
        let node = new LinkNode(item);
        // 将节点的head设置成node
        if (!this.head) {
            this.head = node;
        } else {
            // 如果不是空,则需要找到链表最后一个node,然后将最后一个节点的next设置成当前传入的node
            let currentNode: any = this.getItemAt(this.count - 1);
            currentNode.next = node;
        }
        this.count++;
    }
removeAt(index)

从指定位置移除元素。我们可以先来分析下,移除有三种情况

第一种,只移除头部,只需要将head指向第二个节点即可,同时接触第一个节点next指向第二个节点,所以也就有了这样的形式
image.png

第二种移除尾部,类比移除头部,只需要将倒数第二个节点的next指向为undefined即可,如图
image.png

第三种就是删除中间位置,同理只需要将index所处的节点前一个节点的next指向index所处节点的next节点即可


image.png

具体实现如下
 /**
     * 移除指定位置的节点
     * @param {number} index
     * @returns {LinkNode}
     * @memberof LinkList
     */
    public removeAt(index: number): LinkNode | undefined {
        if (index >= 0 && index <= this.count) {
            let cuttentNode: LinkNode = this.getItemAt(index);
            if (index === this.count - 1) {
                let pretNode: LinkNode = this.getItemAt(index - 1);
                pretNode.next = undefined;
            } else if (index === 0) {
                let headNode: LinkNode = this.getItemAt(0);
                headNode.head = undefined;
            } else {
                let preNode: LinkNode = this.getItemAt(index - 1);
                let currentNode: LinkNode = this.getItemAt(index);
                preNode.next = currentNode.next;
            }
            this.count--;
            return cuttentNode;
        }
        return undefined;
    }
insert(item,index)

在指定位置插入元素。我们来分析分析,我们选择插入元素,那么同移除有三个位置,头部,尾部以及中间

首先我们看下从头部移除,如果头部插入元素的话,那么也就是当前head指向的为插入的元素,插入元素的next指向head以前指向的元素。所以如下图:
image.png

第二种情况,从尾部插入,也就是将当前尾部元素的next指向我们插入的元素,我们插入的元素的next指向原有元素next指向的位置
image.png

第三种情况插入中间位置,那么也就是说假设我们将数据插入第二个位置,我们只需要将第一个位置节点的next指向插入的元素,插入的元素的next指向当前元素即可。如图


image.png

这个时候我们再回顾下从尾部插入,我们是否也可以跟中间节点一样,将最后一个节点的前一个节点的next赋值给当前节点的next,前一个节点的next指向插入的节点,貌似也行,那么代码实现为。
/**
     * 任意位置插入元素
     * @param {*} item
     * @param {number} index
     * @returns {boolean}
     * @memberof LinkList
     */
    public insert(item: any, index: number): boolean {
        if (index >= 0 && index <= this.count) {
            let node = new LinkNode(item);
            if (index === 0) {
                let currentNode = this.head;
                node.next = currentNode;
                this.head = node;
            } else {
                let preNode = this.getItemAt(index - 1);
                node.next = preNode.next;
                preNode.next = node;
            }
            this.count++;
            return true;
        }
        return false;
    }

至此一个普通链表的最重要的三个方法讲完了,下面附上一个链表的具体的实现.下一章我们进入另一个链表-双向链表

/**
 * push
 * insert
 * getItemAt
 * remove
 * indexOf
 * removeAt
 * isEmpty
 * size
 *
 *
 * @export
 * @class LinkList
 */
import LinkNode from './LinkNode'
export default class LinkList {
    public count: number;
    public head: any | undefined;
    constructor() {
        this.count = 0;
        this.head = undefined;
    }
    /**
     *尾部插入元素
     *
     * @param {*} item
     * @memberof LinkList
     */
    public push(item: any) {
        let node = new LinkNode(item);
        // 将节点的head设置成node
        if (!this.head) {
            this.head = node;
        } else {
            // 如果不是空,则需要找到链表最后一个node,然后将最后一个节点的next设置成当前传入的node
            let currentNode: any = this.getItemAt(this.count - 1);
            currentNode.next = node;
        }
        this.count++;
    }
    /**
     * 任意位置插入元素
     * @param {*} item
     * @param {number} index
     * @returns {boolean}
     * @memberof LinkList
     */
    public insert(item: any, index: number): boolean {
        if (index >= 0 && index <= this.count) {
            let node = new LinkNode(item);
            if (index === 0) {
                let currentNode = this.head;
                node.next = currentNode;
                this.head = node;
            } else {
                let preNode = this.getItemAt(index - 1);
                node.next = preNode.next;
                preNode.next = node;
            }
            this.count++;
            return true;
        }
        return false;
    }
    /**
     * 移除指定位置的节点
     * @param {number} index
     * @returns {LinkNode}
     * @memberof LinkList
     */
    public removeAt(index: number): LinkNode | undefined {
        if (index >= 0 && index <= this.count) {
            let cuttentNode: LinkNode = this.getItemAt(index);
            if (index === this.count - 1) {
                let pretNode: LinkNode = this.getItemAt(index - 1);
                pretNode.next = undefined;
            } else if (index === 0) {
                let headNode: LinkNode = this.getItemAt(0);
                headNode.head = undefined;
            } else {
                let preNode: LinkNode = this.getItemAt(index - 1);
                let currentNode: LinkNode = this.getItemAt(index);
                preNode.next = currentNode.next;
            }
            this.count--;
            return cuttentNode;
        }
        return undefined;
    }
    /**
     *  获取指定位置的节点
     * @param {number} index
     * @returns {(LinkNode|undefined)}
     * @memberof LinkList
     */
    public getItemAt(index: number): LinkNode | undefined | any {
        if (index >= 0) {
            if (index > this.count) {
                return undefined;
            } else if (index === 0) {
                return this.head;
            } else {
                let currentNode = this.head;
                while (index && currentNode.next) {
                    currentNode = currentNode.next;
                    index--;
                }
                return currentNode;
            }
        }
    }
    /**
     * 返回item所在的位置
     * @param {*} item
     * @returns {number}
     * @memberof LinkList
     */
    public indexOf(item: any): number {
        let currentNode = this.head;
        let hasFind: boolean = false;
        let index: number = -1;
        while (!hasFind && currentNode) {
            index++;
            if (this.judgeObjectSame(item, currentNode.node)) {
                hasFind = true;
            } else {

                currentNode = currentNode.next;
            }
        }
        if (hasFind) {
            return index;
        } else {
            return -1;
        }
    }
    /**
     * 移除指定元素
     * @param {*} item
     * @memberof LinkList
     */
    public remove(item: any) {
        let index = this.indexOf(item);
        this.removeAt(index);
    }

    /**
     * 获取链表size
     * @returns {number}
     * @memberof LinkList
     */
    public size(): number {
        return this.count;
    }
    /**
     * 判断是否为空
     * @returns
     * @memberof LinkList
     */
    public isEmpty() {
        return this.size() === 0;
    }
    /**
     * 判断两个对象是否相同
     *
     * @param {*} firstObj
     * @param {*} secondObj
     * @returns
     * @memberof StackObject
     */
    public judgeObjectSame(firstObj: any, secondObj: any) {
        const aProps = Object.getOwnPropertyNames(JSON.parse(JSON.stringify(firstObj)));
        const bProps = Object.getOwnPropertyNames(JSON.parse(JSON.stringify(secondObj)));
        if (aProps.length !== bProps.length) {
            return false;
        }
        for (let i = 0; i < aProps.length; i++) {
            const propName = aProps[i]

            const propA = firstObj[propName]
            const propB = secondObj[propName]
            if ((typeof (propA) === 'object') && propA !== null) {
                if (this.judgeObjectSame(propA, propB)) {
                } else {
                    return false;
                }
            } else if (propA !== propB) {
                return false;
            } else { }
        }
        return true;
    }
}

你可能感兴趣的:(算法基础篇-链表)