数据结构5线性标——链式栈

前言:

本专栏属于数据结构相关内容,附带一些代码加深对一些内容的理解,为方便读者观看,本专栏内的所有文章会同时附带C语言和Python对应的代码,(可自行通过目录跳转到对应的部分)辅助不同主修语言的读者去更好的理解对应的内容,若是代码0基础的读者,可先去博主其他专栏学习一下基础的语法及知识点:

魔法天才的跳转链接:

C语言:C基础_Gu_shiwww的博客-CSDN博客

Python语言:python1_Gu_shiwww的博客-CSDN博客

其他数据结构内容可见:数据结构_Gu_shiwww的博客-CSDN博客

链式栈的特点

逻辑结构:线性结构

物理结构:链式结构

数据特点:后进先出

1 基本概念

栈具有后进先出的特点,我们使用链表来实现栈,即链式栈。那么栈顶是入栈和出栈的地方,单向链表有头有尾,那我们将链表的头作为栈顶还是链表的尾作为栈顶呢?如果每次在链表的尾部进行插入或删除,就需要遍历整个链表来找到尾结点即终端结点。而在头部即起始结点进行插入或删除时,仅需头引用找到链表的起始结点,而无需遍历整个链表。

所以链式栈的入栈、出栈都是通过对链表进行头插、头删来实现

1.* 顺序栈和链式栈的区别

存储结构不同,那么实现的方式也不同,顺序栈用数组也就是顺序表实现,内存连续,长度固定; 链式栈用链表实现,内存不连续,长度不固定。

2 编程实现链式栈

C语言编程实现链式栈

C.* 函数接口

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

typedef int datatype;
typedef struct node_t
{
    datatype data;
    struct node_t *next;
} link_node_t, *link_node_p;

//创建一个空栈
void createEmptyLinkStack(link_node_p *p); //p=&top

//入栈, data是入栈的数据
int pushLinkStack(link_node_p *p, datatype data);

//判断栈是否为空
int isEmptyLinkStack(link_node_p top);

//出栈
datatype popLinkStack(link_node_p *p); //p=&top =>*p=top

//求栈的长度
int lengthLinkStack(link_node_p top);

C.1 创建一个空栈

cpp 复制代码
//创建一个空栈
void createEmptyLinkStack(link_node_p *p) //p=&top
{
    *p = NULL; //*p = top =NULL
}

将定义好的某结构体内的数据赋为NULL,即初始化空

C.2 入栈

python 复制代码
//入栈, data是入栈的数据
int pushLinkStack(link_node_p *p, datatype data) //参数上之所以采用二级指针,因为我们要//随着入栈添加新的节点作为头,top需要永远指向当前链表的头//那么修改main函数中的top,我们能采用地址传递。
{
    //1. 新建节点
    link_node_p pnew = (link_node_p)malloc(sizeof(link_node_t));
    if (NULL == pnew)
    {
        perror("pnew malloc err");
        return -1;
    }
    //2. 初始化新节点
    pnew->data = data;

    //3. 连接新节点到链表
    pnew->next = *p; //p=&top ==> *p=top

    //4. 移动弄栈针到新节点
    *p = pnew;  //也就是top指向了pnew,因为*p就是top

    return 0;
}

入栈之前要首先用malloc开辟一个节点,为提高代码健壮性判断节点是否开辟成功。将入栈传入的形参data值传入新开辟的pnew的data域,然后将传入的p解引用连接到pnew的next域,这样就实现了链表栈的连接操作,此时指针p的功能可以视作顺序表中的top指针,从该位置开始遍历整个栈且完成入栈出栈操作。故p指针也要向前移动到pnew出。因为传入的p是一个指针变量的地址,解引用之后表示指针变量的存储空间,可以存储一个指针,也就可以将pnew传给p

C.3 判空

cpp 复制代码
//判断栈是否为空
int isEmptyLinkStack(link_node_p top)
{
    return top == NULL;
}

top是一个结构体指针变量,外部传参时传递的是链式栈的top指针。当这个指针的指向为空的时候,证明栈内无任何数据。可将此判断结果作为函数的返回值。(注意,链式栈理论上来讲可无限将节点链接起来,不需要判满)

C.4 出栈

cpp 复制代码
//出栈
datatype popLinkStack(link_node_p *p) //p=&top =>*p=top
{
    //1. 判空
    if (isEmptyLinkStack(*p))
    {
        printf("is empy\n");
        return -1;
    }
    //2. 设指针pdel指向要释放节点
    link_node_p pdel = *p;

    //3. 设变量temp保存要出栈数据
    datatype temp = (*p)->data; //或者temp = pdel->data

    //4.将栈针向后移动一个单位
    *p = (*p)->next; //*p = pdel->next;

    //5. 释放要删除节点
    free(pdel);

    //6.返回要出栈数据
    return temp;
}

链式栈与顺序栈不同,出栈意味着元素被舍弃,不能进行逻辑删除,同时要确定节点在内部的物理地址中也被删除。出栈操作前要先对栈进行判空操作,空栈无数据可出。

在函数内部定义一个结构体指针pdel指向栈顶,用一个变量temp存储栈顶元素(*p)->data,然后将指针p向后移动。此时原来的栈顶结点与链式栈断开,但是依旧可以通过pdel访问到该节点。最后free(pdel)完成出栈操作(若无pdel,被断开的节点就无法被找到,可能会造成内存泄露)

C.5 求栈长度

cpp 复制代码
//求栈的长度
int lengthLinkStack(link_node_p top)
{
    int len = 0;
    while (top != NULL)
    {
        len++;
        top = top->next;
    }
    return len;
}

传入栈顶指针(传入的虽然是地址,但是该变量在外部主函数中也是一个指针变量的形式,改变函数内部的这个指针不会对主函数中的指针发生改变,若想改变主函数中指针的指向,需要获取到该指针变量的地址,定义一个二级指针,且在外部调用的时候,需要用&获取指针变量的地址。),在函数内部定义一个变量len作为栈长度的存储变量。当top的内容不为空的时候,需要将top指向后移,直到top不再指向具体内容的时候循环停止。最终函数返回len,即整个链式栈的长度

C.6 完整代码(可运行)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

typedef int datatype;
typedef struct node_t
{
    datatype data;
    struct node_t *next;
} link_node_t, *link_node_p;

//创建一个空栈
void createEmptyLinkStack(link_node_p *p) //p=&top
{
    *p = NULL; //*p = top =NULL
}

//入栈, data是入栈的数据
int pushLinkStack(link_node_p *p, datatype data) //参数上之所以采用二级指针,因为我们要//随着入栈添加新的节点作为头,top需要永远指向当前链表的头//那么修改main函数中的top,我们能采用地址传递。
{
    //1. 新建节点
    link_node_p pnew = (link_node_p)malloc(sizeof(link_node_t));
    if (NULL == pnew)
    {
        perror("pnew malloc err");
        return -1;
    }
    //2. 初始化新节点
    pnew->data = data;

    //3. 连接新节点到链表
    pnew->next = *p; //p=&top ==> *p=top

    //4. 移动弄栈针到新节点
    *p = pnew;  //也就是top指向了pnew,因为*p就是top

    return 0;
}

//判断栈是否为空
int isEmptyLinkStack(link_node_p top)
{
    return top == NULL;
}

//出栈
datatype popLinkStack(link_node_p *p) //p=&top =>*p=top
{
    //1. 判空
    if (isEmptyLinkStack(*p))
    {
        printf("is empy\n");
        return -1;
    }
    //2. 设指针pdel指向要释放节点
    link_node_p pdel = *p;

    //3. 设变量temp保存要出栈数据
    datatype temp = (*p)->data; //或者temp = pdel->data

    //4.将栈针向后移动一个单位
    *p = (*p)->next; //*p = pdel->next;

    //5. 释放要删除节点
    free(pdel);

    //6.返回要出栈数据
    return temp;
}

//求栈的长度
int lengthLinkStack(link_node_p top)
{
    int len = 0;
    while (top != NULL)
    {
        len++;
        top = top->next;
    }
    return len;
}

int main(int argc, char const *argv[])
{
    link_node_p top;
    createEmptyLinkStack(&top); //调用函数结束后 top=NULL
    pushLinkStack(&top, 1);
    pushLinkStack(&top, 2);
    pushLinkStack(&top, 3);
    printf(" len = %d\n", lengthLinkStack(top));

    while (!isEmptyLinkStack(top))
        printf("%d\n", popLinkStack(&top));  //3 2 1
    return 0;
}

Python语言编写链式栈

P.* 函数接口

python 复制代码
class Node:
    """链式栈节点类"""

    def __init__(self, data):
        self.data = data
        self.next = None


class LinkStack:
    def __init__(self):
        self.top = None

    # 1.入栈,data是入栈的数据
    def push(self, data):
        pass
    # 2.判断栈是否为空
    def is_empty(self):
        pass
    # 3.出栈
    def pop(self):
        pass
    # 4.清空栈
    def clear(self):
        pass
    # 5.求栈的长度
    def length(self):
        pass
    # 6.获取栈顶数据,不是出栈,不需要移动top
    def get_top(self):
        pass

if __name__ == '__main__':
    pass

P.1 入栈

每次都将新节点连接到无头单向链表的头

栈顶top永远指向无头单向链表的头,栈空时除外

python 复制代码
# 1.入栈,data是入栈的数据
    def push(self, data):
        new_node = Node(data)
        new_node.next = self.top
        self.top = new_node

建立一个新节点new_node存储传入的形参数据data,用指针连接到top后面

P.2 判空

python 复制代码
# 3.判断栈是否为空
def is_empty(self):
    return self.top is None

即判断top是否为空,可将判断式的结果作为函数返回值

P.3 出栈

python 复制代码
# 4.出栈
def pop(self):
    if self.is_empty():
        print("pop error")
        return
    data = self.top.data
    self.top = self.top.next
    return data

首先容错判断,若栈为空则无数据可出栈。定义一个data变量存储出栈节点的数据,将top指针移到top的下一个位置,data作为返回值可以看看本次出栈的数据是什么(Python中不需要单独释放被断开的节点,会用垃圾回收机制自动回收)

P.4 清空栈

python 复制代码
# 5.清空栈
def clear(self):
    self.top = None

清空,只需要将top的数据赋为空,就可以将top与后续所有的节点断开,后续节点会由Python的垃圾回收机制自动回收

P.5 求栈长

python 复制代码
# 6.求栈的长度
def length(self):
    current = self.top
    length = 0
    while current:
        length += 1
        current = current.next
    return length

首先current指向top,从top开始依次遍历,定义一个length变量,当current内不为空的时候,while循环执行,current后移,length+1,最后把length的值作为返回值

P.6 获取栈顶数据

python 复制代码
# 7.获取栈顶数据,不是出栈,不需要移动top
def get_top(self):
    if self.is_empty():
        print("get_top error")
        return
    return self.top.data

首先容错判断,若栈为空则无栈顶无数据。若栈不空直接返回top对应的data域中的数据

P.7 完整代码(可执行)

python 复制代码
class Node:
    """链式栈节点类"""

    def __init__(self, data):
        self.data = data
        self.next = None


class LinkStack:
    def __init__(self):
        self.top = None

    # 1.入栈,data是入栈的数据
    def push(self, data):
        new_node = Node(data)
        new_node.next = self.top
        self.top = new_node

    # 2.判断栈是否为空
    def is_empty(self):
        return self.top is None

    # 3.出栈
    def pop(self):
        if self.is_empty():
            print("pop error")
            return
        data = self.top.data
        self.top = self.top.next
        return data

    # 4.清空栈
    def clear(self):
        self.top = None

    # 5.求栈的长度
    def length(self):
        current = self.top
        length = 0
        while current:
            length += 1
            current = current.next
        return length

    # 6.获取栈顶数据,不是出栈,不需要移动top
    def get_top(self):
        if self.is_empty():
            print("get_top error")
            return
        return self.top.data


if __name__ == '__main__':
    link_stack = LinkStack()
    for i in range(6):
        link_stack.push(i)
    print("top_data",link_stack.get_top())
    print("stack_length",link_stack.length())
    for _ in range(6):
        print("data", link_stack.pop())