读一读

当有那么一种需求,就是需要两个栈存储,一个增加,另一个就减少时,可以考虑使用两栈共享空间。

就是一个数据域(数组表示),两个下标来标识两个的栈顶,当标识重叠时,就是栈满了。

栈的顺序结构,两栈共享空间与链式结构


栈其实是一个受限的线性表,只允许后进先出的概念,也就是出栈和入栈。

顺序存储结构也就是用一个数组来存储栈中的数据,数组实际数据域的末尾保存栈顶数据,用一个栈顶指标top保存这个末尾的下标。

出栈时,获取到当前top下标的数据,将top往前指。入栈时,将数据存储到目前栈顶top的下一位,然后将top往后指。

当然的,就是这个是用数组存储的,所以需要注意栈的大小。

TIM图片20180428103312.png


所谓的双向链表就是可以通过任意一结点获取到它的前驱和后继。普通的单节点只有一个next的引用,用来指向下一个元素,而找不到它的上一个元素。而双向链表就是结点不一样,加多了一个pre的引用,用来指向上一个元素。

所以,在插入和添加等操作的时候,赋值next指针的同时,也需要多维护一下pre指针。


循环链表和普通链表差不多,就是循环链表的最后一个结点的下一个结点引用不为空了,而是为头结点。这样就可以通过遍历到的尾结点又回到头结点处。

它的好处就是可以通过任一结点,就可以完成链表的遍历了。


静态链表就是使用数组的方式来实现线性表的链式结构。它存储元素的个数是受限的。

主要用于没有指针引用等概念的编程语言。

思路就是有一个节点类型,是结构类型的值类型,保存数据和下一个元素的数组下标。数据域就是一个结点数组,比存储的数据大小多出两个节点来,第一个结点和最后一个节点,分别作为两条链表,一条用来保存没有用过的结点,一条用来保存数据结点,头末结点就是这两条链表的头结点,当需要添加元素,就从备用链表中获取到空闲结点,放置到数据链表中。删除时同样如此,将数据结点移除,放置到备用链表中。用下标0表示链表的结束。

TIM图片20180427142700.png


//通过索引删除元素
public T DeleteByIndex(int index)
{
    Node go = this.GetNodeByIndex(index - 1);

    if (go == null || go.next == null)
    {
        Console.WriteLine("索引非法!");
        return default(T);
    }

    Node p = go.next;
    T old = p.data;
    go.next = p.next;
    p = null;

    if (index == this._curentLinkLength)
    {
        //说明删除的是最后一个元素
        this._last = go;
    }
    this._curentLinkLength--;
    return old;
}

线性表的链式存储结构


同样的,删除指定位置的元素也是需要获取到这个位置的前一个元素的,同时还要获取到后一个元素,可以通过前一个元素的next的next获取到,然后直接修改前一个元素的next为后一个元素,那么这个要删除的位置的元素的引用就没有了,也就是凭空消失了。

如果有尾结点引用,需要注意的是删除最后一个结点的情况,需要修改尾结点的引用为前一个元素。


//向链表索引i前插入元素
public bool Insert(int index, T element)
{
    Node go = this.GetNodeByIndex(index - 1);

    if (go == null || go.next == null)
    {
        Console.WriteLine("索引非法!");
        return false;
    }

    Node add = new Node();
    add.data = element;
    add.next = go.next;
    go.next = add;
    this._curentLinkLength++;

    return true;
}

线性表的链式存储结构


要在指定位置前插入元素,那么就要得到插入元素的前一个元素,这样就可以设置新加元素的下一个元素为index位置结点(就是pre结点的next结点),然后设置pre的next结点为新的结点就行了。


//向链表头部增加元素
public bool AddToHead(T element)
{
    Node add = new Node();
    add.data = element;
    add.next = _head.next;
    if (_head.next == null)
    {
        //判断是否是空链表,如果是,尾结点就是插入结点
        this._last = add;
    }

    _head.next = add;
    this._curentLinkLength++;
    return true;
}


在链表前面添加元素,这时候头结点的作用就来了,设置新结点的下一个结点为头结点的下一个结点(当前的第一个节点),修改头结点的下一个结点(第一个结点)为新结点。

注意如果有尾结点时,注意空链表时往链表前面添加元素的情况。


//向链表后面快速添加元素
public bool AddQuick(T element)
{

    Node add = new Node();
    add.data = element;
    add.next = null;

    this._last.next = add;
    this._last = add;
    this._curentLinkLength++;
    return true;

}

线性表的链式存储结构


直接使用尾部结点的引用,快速赋值next结点为新结点,再改变尾部结点为新结点。

如果没有尾部结点,则需要找到这个结点,通过遍历整个链表,直到结点的next引用为空,那么这个结点就是尾结点。


//获取指定位置的节点
private Node GetNodeByIndex(int index)
{
    Node go = this._head;
    int i = 0;
    while (go != null && i < index)
    {
        go = go.next;
        i++;
    }
    
    if(i != index)  go = null;

    return go;
}

线性表的链式存储结构


因为是链式结构,不能像数组那样直接下标获取元素了,只能遍历整个链表,一个一个元素往下,知道指定的位置。