挑战408——数据结构(14)——线性结构(5)

前篇讨论了一下具体的链表结构以及常规操作。现在我们趁热打铁,感觉再来熟悉一下具体的操作。此时我们用链表的形式来实现之前的顺序栈。

链栈

栈结构这里不多说,我们直接进入正题。先考虑,当栈为空的时候,如何表示?当然就是一个空指针了
在这里插入图片描述
将新元素推入堆栈时,该元素只会添加到链表的前端。因此,如果将元素e1 push到空栈上,该元素将存储在一个新单元格中,该单元格将成为链中唯一的链接:
在这里插入图片描述
将一个新元素push栈将该元素添加到链的开头。所涉及的步骤与将字符插入链表列表缓冲区所需的步骤相同。 首先分配一个新的单元格,然后输入数据,最后更新链接指针,使新单元格成为链中的第一个元素。 因此,如果将元素e2推到栈上,将得到以下配置:
在这里插入图片描述
在链表表示中,pop操作包括删除链中的第一个单元格并返回存储在该列中的值。因此,来自上图所示的栈的pop操作返回e2并恢复栈的先前状态,如下所示:
在这里插入图片描述

对链栈中的pop和push的分析
push

我们以一个最基本的数字栈为基础分析,下面是需要的一些接口内容:

class StackInt {          // in StackInt.h
public:
    StackInt ();          // constructor

    void push(int value); // append a value 
    int pop();            // return the first-in value

private:
    struct Node {
      int value;
      Node * link;
    };
    Node * data;     // member variables
};

接下来看代码分析:

void StackInt::push(int v) {
    Node * temp = new Node;
    temp->value = v;
    temp->link = data;  //这一步必不可少!
    data = temp;
}

老办法,图解push过程,假设此时我们的链栈如下:
在这里插入图片描述
我们的目的是push(7),将7放入栈顶,也就是要这样的结果:
在这里插入图片描述
下面解释整个过程:

  1. 新建一个临时节点,并用创建临时指针指向该节点,接着向该节点写入数字7,就像这样:
    在这里插入图片描述
  2. 然后,将头指针data里面的指针值,赋值给新节点的link字段,以便新节点可以连接到8这个节点:
    在这里插入图片描述
  3. 将temp里面的值,赋值给首指针data处。将data的指向正确改变:
    在这里插入图片描述
  4. 函数返回,得出正确的push后的结构:在这里插入图片描述
pop

先看代码:

int StackInt::pop() {
    int toReturn = data->value;
    Node * temp = data;
    data = temp->link;
    delete temp;
    return toReturn;
}

图解过程如下:

  1. 先将要pop出去的值进行保存
    在这里插入图片描述
  2. 我们这边创建一个临时的指针变量Temp,然后将其指向首指针data指向的节点(即栈顶元素),就像这样:
    在这里插入图片描述
  3. 然后改变首指针(即栈顶)的指向:
    在这里插入图片描述
  4. 最后删除temp指向的节点
    在这里插入图片描述
  5. 此时函数返回。

注意,我们第二步不可以直接改变指针的指向而不使用临时节点,因为直接改变指针的指向会像下图一样:
在这里插入图片描述
这显然不是我们希望看到的。

链队列

队列类也使用链表结构进行简单的表示。为了说明基本的方法,队列的元素存储在从队列头部开始并以尾部结尾的列表中。并且为了保证入队和出队正确运行,Queue对象必须含有指向队列两端的指针。 因此,私有实例变量的定义在queuepriv.h的修改版本如下:

/*
 *这个文件保存的是Queue.h的私有部分
 *我们这个时候用链队列。也就是用链表来实现我们的队列
 *这样的队列我们称为链队列
 */
private:
    /*队列的链式结构*/
    struct Cell{
        ValueType data;
        Cell *link;
    };
    /*实例化变量*/
    Cell *head; //头指针
    Cell *tail; //尾指针
    int count; //记录队列元素的总数


    /* Make it illegal to copy queues */
    Queue(const Queue & value) { }
    const Queue & operator=(const Queue & rhs) { return *this; }

那么为什么我们要设置一个尾指针呢?
首先我们假设一下我们初始化的队列如下所示:
在这里插入图片描述
这个时候我们执行后入队操作:
在这里插入图片描述
显然,这个时候我们相当于在链表中插入一个元素,且位于头结点的第一个,自然我们的复杂度为O(1)。但是当我们执行出队操作时,情况就不同了,我们必须验证头指针遍历到最后一个节点,再执行delet操作,此时算法复杂度为O(N)。

入队(enqueue)

那么是否有更好的办法呢?因此我们考虑一下添加一个尾指针(因为队列多数在链表的两端操作)。所以我们看下面一段代码:

void QueueInt::enqueue(int v) {
    Node * temp = new Node;
    temp->value = v;
    tail->link = temp;
    tail = temp;
}

图解代码执行过程:

  1. 假设我们初始的队列值为123,并且我们添加的尾指针指向的是我们的3的节点,如图:
    在这里插入图片描述
  2. 此时我们在队列尾部(3),这里加入数值4,此时,我们的操作应该是,建立一个指针,指向新建立的节点,然后往这个新节点写入数值:
    在这里插入图片描述
  3. 接下来我们改变指针的指向,也就是将cp的内容复制到3下面的link字段(也就是tail指向的节点的link字段)。此时情况如下(分两步):
    在这里插入图片描述
    在这里插入图片描述
  4. 最终我们返回我们的队列就可以了,因此复杂度为O(1)
    在这里插入图片描述
出队(dequeue)

我们就拿上面完成的例子为例,进行我们的dequeue操作,我们先看2 3行代码,建立一个临时节点,指向与我们的head指针的指向相同,如下:
首先假设我们有完整的队列
在这里插入图片描述
现在我们要进行的queue操作,于是将队头移除,以达到下图的结果:
在这里插入图片描述
同理,看代码:

int QueueInt::dequeue() {
    int toReturn = head->value;
    Node * temp = head;
    head = temp->link;
    delete temp;
    return toReturn;
}

  1. 我们先将此时队头指向的数字给存储起来,然后新建一个指针,跟队头指针一同指向首元素1.
    在这里插入图片描述
  2. 然后改变头指针的指向
    在这里插入图片描述
  3. 释放被dequeue掉的那个节点
    在这里插入图片描述
算法复杂度分析

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值