数据结构学习笔记

前言

参考资料:数据结构-小美老师https://www.bilibili.com/video/BV12Y411b76j

0.引入

数据结构是研究计算机数据之间的关系,包括数据的逻辑结构存储结构运算****操作

1.数据的逻辑结构

表示数据运算之间的抽象关系,按照每个元素可能具有的直接前驱数和直接后驱数将逻辑结构划分为:

  • 线性结构
  • 非线性结构

具体包括:

1)集合——数据元素间除“同属于一个集合”外,无其他关系

[

2)线性结构——一个对一个,如线性表、栈、队列

在这里插入图片描述

3)树形结构——一个对多个,如树
在这里插入图片描述

4)图状结构——多个对多个,如图

2.数据的存储结构

存储结构:逻辑结构再计算机中的具体实现方法

存储结构是通过计算机程序来实现的,因而引来于具体的计算机语言

存储结构类型包括:

1)顺序存储

数据结构中各元素按照逻辑顺序存放在一片连续的存储空间中,各个元素的地址是相邻的

如c语言中的一维数组,如表 L=(a1,a2,……,an)的顺序结构

在这里插入图片描述

2)链式存储

将数据结构中各元素分布到存储器的不同点,用**地址(或链指针)**方式建立它们之间的联系,数据结构中元素之间的关系在计算机内部很大程度上是通过地址或指针来建立的,如下所示

在这里插入图片描述

3)索引存储

在存储数据的同时,建立一个附加的索引表,即索引存储结构=数据文件+索引表。
在这里插入图片描述

4)散列存储

根据数据元素的特殊字段(称为关键字key),计算数据元素的存放地址,然后数据元素按地址存放
在这里插入图片描述

总结:

数据结构的核心内容如下图所示,从逻辑结构、存储结构和数据的操作运算三个方面展开

在这里插入图片描述

1.线性表

线性表在逻辑结构上属于线性结构,即每个元素只有一个前驱,一个后继(首尾元素除外)

线性表是包含若干数据元素的一个线性序列

记为: L=(a0,… ai-1, ai, ai+1 …an-1)

L为表名,ai(0≤i≤n-1)为数据元素;

n为表长,n>0时,线性表L为非空表,否则为空表。

在这里插入图片描述

线性表的特征:

  • 对非空表,a0是表头,无前驱
  • an-1是表尾,无后继
  • 其他的每个元素ai有且仅有一个直接前驱ai-1和一个直接后继ai+1

线性表根据存储结构的不同,可分为

  • 顺序表(顺序存储)
  • 链表(链式存储)

1.顺序表

1.基本概念

顺序存储的线性表称为顺序表,顺序表既保留了线性表的前驱和后继特征,同时又具有顺序存储结构。

若将线性表L=(a0,a1, ……,an-1)中的各元素依次存储于计算机一片连续的存储空间,如:
在这里插入图片描述

顺序存储结构特点:

  • 逻辑上相邻的元素ai,ai+1,其存储位置也是相邻的
  • 对数据元素ai的存取位随机存取或按地址存取
  • 存储密度高
  • 缺点:对表的插入和删除等运算的时间复杂度较差

顺序存储结构的表示

如下所示,使用一维数组表示顺序存储结构,同时添加了一个last表示数据元素的数量,两者共同组成一个结构体

在这里插入图片描述

2.顺序表编程实现

本节将使用c语言实现顺序表的一些操作,包括常见的增删改查等。

如下所示为程序的基本组成结构,sqlist.c和sqlist.h分别为顺序表操作的源文件和头文件,其中包括了一些具体的操作函数。test.c是用于测试顺序表操作的测试程序。

在这里插入图片描述

sqlist.h程序如下所示为定义顺序表的sqlist.h头文件,前面7行定义了数据类型,元素的数量,最重要的是利用typedef定义了一个结构体sqlist和结构体指针sqlink,之后就可以直接使用sqlist和sqlink定义结构体和结构体指针变量。在这个结构体中包括两个成员,第一个就是顺序表所在的一维数组,第二个last变量用于表示数组中元素的最大索引值,比如有10个数组元素,则last为9。

9行之后的内容就是一些顺序表的操作函数声明,包括创建顺序表,删除表中元素,插入元素,求两个表的并集等,具体的函数在sqlist.c中定义。对于每个操作函数的说明也会在源文件中介绍。

typedef int data_t;
#define N 128 

typedef struct {
	data_t data[N];
	int last;
}sqlist, *sqlink;

sqlink list_create();//创建顺序表
int list_clear(sqlink L);//清空顺序表
int list_free(sqlink L);//释放顺序表
int list_empty(sqlink L);//判断是否为空
int list_length(sqlink L);//表元素数量
int list_locate(sqlink L, data_t value);//定位顺序表中某个元素位置
int list_insert(sqlink L, data_t value, int pos);//在表中指定位置插入元素
int list_delete(sqlink L, int pos);//删除表中指定位置的元素
int list_merge(sqlink L1, sqlink L2);//两个顺序表求并集
int list_purge(sqlink L);//清除表中重复元素
int list_show(sqlink L);//打印输出表中所有元素

下面将结合顺序表的函数定义对具体的操作进行介绍

1.顺序表的创建、清空、释放、是否为空、元素数量

1)顺序表的创建

在对顺序表进行运算操作之前,首先要定义一个顺序表。如下所示,此处创建的顺序表就是之前定义的结构体,但是这里使用的是结构体指针。首先是定义了一个对应类型的结构体指针变量L,因为只是一个地址,所以为了使其可以存放数据,需要使用malloc申请动态内存,malloc函数会返回一段内存的地址,可以设定该段地址的大小,此处设定大小为结构体所占内存大小。申请完成后,L中存放的就是这段内存的地址,通过sqlink强制转换,则表明指向的内存地址是存放该结构体类型的数据。如果申请内存失败,会放回一个NULL,具体判断是否申请成功。

以下程序中的memset函数的作用是对指定地址的内存空间数据清零,在申请好内存后,内存中可能存在其他的数据,可以使用memset全部清零。

最后返回一个结构体指针,在主程序中会获得这个指针,之后就可以对该指针对应的结构体进行操作。

sqlink list_create() {
	//malloc
	sqlink L;

	L =(sqlink)malloc(sizeof(sqlist));
	if (L == NULL) {
		printf("list malloc failed\n");
		return L;
	}

	//initialize
	memset(L, 0, sizeof(sqlist));
	L->last = -1;

	//return
	return L;
}

2)清空、释放、是否为空、元素数量

该部分的操作比较简单,如下所示,清空表就是使用memset把这段内存中的数据全部清零。释放表就是使用free函数释放这段内存空间。

/*清空顺序表*/
int list_clear(sqlink L) {
	if (L == NULL)
		return -1;

	memset(L, 0, sizeof(sqlist));
	L->last = -1;

	return 0;
}
/*释放顺序表*/
int list_free(sqlink L){
	if (L == NULL) 
		return -1;
	free(L);
	L = NULL;
	return 0;
}

下面的程序分别是判断是否为空和获取表中元素数量,采用的方法都是借助结构体中的last来判断和得到元素的数量。

/*判断是否为空*/
int list_empty(sqlink L) {
	if (L->last == -1) 
		return 1;
	else 
		return 0;
}
/*表中元素数量*/
int list_length(sqlink L) {
	if (L == NULL) 
		return -1;
	return (L->last+1);
}

2.定位元素、指定位置插入、指定位置删除

如下为定位一个元素所在位置的操作函数。其实采用的方法很简单,直接从第一个元素开始遍历,同时判断该元素是否和需要定位的元素是否相等,如果相等则返回对应的元素位置索引,如果遍历所有元素后不存在则返回-1。

int list_locate(sqlink L, data_t value) {
	int i ;
	for (i = 0; i <= L->last; i++) {
		if (L->data[i] == value) 
			return i;
	}

	return -1;
}

在指定位置插入元素的函数如下所示,传入的参数包括:需要插入的原始顺序表,要插入元素数据,插入的位置

首先第一步是判断这个顺序表是否满,可以利用last的数值判断,如果顺序表已满则直接返回-1.

第二步是判断插入的位置是否在指定范围之内,如果插入的位置在范围之外则直接返回-1

第三步的作用是将待插位置之后的元素往后移动,如果直接插入会覆盖原有的元素,所以需要先移动再插入,这个不难理解;此外这里还要注意,在移动时采用的是尾部元素先移动的方式,这个也很好理解,如果先移动前面的元素,之后的元素也会被覆盖掉。此处采用一个for循环完成各个元素的往后移动。

最后一步就是插入待插入的元素,并且last数值+1,表示表中的元素增加了一个

int list_insert(sqlink L, data_t value, int pos) {
	int i;

	//判断是否已满
	if (L->last == N-1) {
		printf("list is full\n");
		return -1;
	}
        //判断插入位置是否在一定范围之内
	//check para    0<=pos<=Last+1   [0, last+1]
	if (pos < 0 || pos > L->last+1) {
		printf("Pos is invalid\n");
		return -1;
	}

	//将待插位置之后的元素往后移动
	for (i = L->last; i >= pos; i--) {
		L->data[i+1] = L->data[i];
	}

	//插入待插元素数据
	L->data[pos] = value;
	L->last++;

	return 0;
}

删除指定位置的元素操作函数如下,其方法和上述插入元素的方法基本类似,这里是直接将待删元素位置后的元素往前移动即可,这种操作可以直接覆盖掉需要删除的元素,注意这里的操作顺序是先移动前面的元素。最后last-1,表示数据量少了一个。

dhuint list_delete(sqlink L, int pos) {
	int i;

	if (L->last == -1) {
		printf("list is empty\n");
		return -1;
	}

	//pos [0, last]
	if (pos < 0 || pos > L->last) {
		printf("delete pos is invalid\n");
		return -1;
	}

	//move  [pos+1, last]
	for (i = pos+1; i <= L->last; i++) {
		L->data[i-1] = L->data[i];
	}

	//update
	L->last--;

	return 0;
}

3.顺序表求并集、删除重复元素

两个顺序表求解并集的程序如下,求并集包括两个元素,一方面是两个顺序表合并成一个表,同时要避免有重复的元素。

下面的程序是把L2合并在L1之后,所以在while循环中的操作是把L2中的元素放在L1后面,同时需要使用list_locate判断L1中是否有重复的元素,如果不重复,则使用后list_insert进行插入,每次插入到L1的末尾。重复以上过程,直到把L2中非重复的元素全部插入L1中,最终实现两个顺序表求并集。

int list_merge(sqlink L1, sqlink L2) {
	int i = 0;
	int ret;

	while (i <= L2->last){
		ret = list_locate(L1, L2->data[i]);
		if (ret == -1) {
			if (list_insert(L1, L2->data[i], L1->last+1) == -1) 
				return -1;
		}

		i++;
	}
	return 0;
}

删除重复元素的方法如下,基本思想是判断每一个元素是否和其他元素重复,所以这里包含了两个循环,最外层循环是一次获取表中的元素,第二个循环实现的操作时和该元素之前的所有的元素做比较,如果有重复的元素则删除掉。重复以上过程,最终可以删除掉所有的重复元素

int list_purge(sqlink L) {
	int i;
	int j;

	if (L->last == 0)
		return 0;

	i = 1;
	while (i <= L->last) {
		j = i-1;
		while (j >= 0) {
			if (L->data[i] == L->data[j]) {
				list_delete(L, i);
				break;
			} else {
				j--;
			}
		}

		if ( j < 0) {
			i++;
		}
	}

	return 0;
}

在这里插入图片描述

2.链表

1.基本概念

链式存储的线性表称为链表,链表包括单链表,循环链表,双向循环链表等,其中单链表最为常见

单链表在逻辑结构上同样属于线性表,有一个前驱和一个后继;

单链表和顺序表的主要区别就是存储结构不同,顺序表采用顺序存储结构,单链表采用指针的方式进行链式存储。

链表的存储结点组成

将线性表L=(a0,a1,……,an-1)中各元素分布在存储器的不同存储块,称为结点,通过地址或指针建立元素之间的联系。结点的data域存放数据元素ai,而next域是一个指针,指向ai的直接后继ai+1所在的结点
在这里插入图片描述


在这里插入图片描述

带头结点的链表,如下所示,第一个结点数据域中没有数据,而指针域存放的是a0元素所在的结点地址。所以称这种结点为头结点;类似还有一个尾结点,该节点中有数据,但是由于是最后一个元素,所以指针域中没有数据。

在这里插入图片描述

和顺序表类似,链表也有对应的数据形式,如下所示,一个结点使用一个结构体表示,结构体中包括结点的数据data和指针域 nexe。注意这里因为每个结点都是结构体,所以这里使用的指针是结构体指针。

在这里插入图片描述

比如:

在这里插入图片描述

2.链表编程实现

和顺序表类似,代码结构上也包括下图三个部分,linklist中包含了实现链表增删改查等基本操作的函数定义和声明,test是测试程序

在这里插入图片描述

首先看一下在linklist.h头文件中有哪些链表的操作,如下所示。定义了一个链表结点的结构体,和顺序表的类似,但结构体中还定义了一个结构体指针next,用于指向下一个结点。

下面是一些链表操作的函数声明,包括创建链表,插入结点元素,删除结点元素等,具体的函数在linklist.c中定义。

typedef int data_t;

typedef struct node {
	data_t data;
	struct node * next;
}listnode, * linklist;

linklist list_create();//创建链表
int list_tail_insert(linklist H, data_t value);//在尾部插入
linklist list_get(linklist H, int pos);//获取对应位置处的元素
int list_insert(linklist H, data_t value, int pos);//将元素插入到指定位置
int list_delete(linklist H, int pos);//删除对应位置处元素
int list_show(linklist H);//遍历显示所有链表元素
linklist list_free(linklist H);//释放链表内存空间

1.链表的创建

链表的创建函数如下,和顺序表的创建基本没有区别,同样是使用malloc申请内存,得到一个对应的结构体指针,注意这里得到的指针是指的头结点H的指针,之后的H统一表示头结点的指针;头结点的data是0,next是NULL,这是头结点的初始状态,之后会在后面插入结点元素,到时next就会指向后面的结点。

linklist list_create() {
	linklist H;

	H = (linklist)malloc(sizeof(listnode));
	if (H == NULL) {
		printf("malloc failed\n");
		return H;
	}

	H->data = 0;
	H->next = NULL;

	return H;
}

2.链表尾部插入

下面的函数是将一个结点插到链表的尾部。函数的参数包括头结点指针H,以及要插入的结点数据data。

为了便于理解,下面针对两种情况分析,首先是只有头结点的情况下插入第一个结点数据。

具体操作上,首先是申请一个结点内存p,如下所示,其中的data存放的就是要插入元素的数据,next为NULL,因为是尾插入,所以是NULL。在只有头结点的情况下,为了建立链表的关系,则头结点的H->next指针应该指向p,所以有H->next=p。由此就完成了在头结点后面插入一个结点元素。

第二种情况是针对多个结点的情况,即链表中已经有了其他的结点元素,这是新的结点也需要插在末尾,这种情况如何实现。

如下所示,这里使用了一个媒介q,初始的时候,q就是头结点H,之后判断q->next指针是否为NULL,如果是NULL表明这是链表中的最后一个元素,此时就可以在这个元素后面插入结点。

如何找到最后一个结点呢?下面程序的while循环中采用了一种迭代的思想,使用q=q->next,使得指针依次往后变化,直到找到最后一个元素地址后停止,最后就可以在这个尾结点后插入元素。

int list_tail_insert(linklist H, data_t value) {
	linklist p;
	linklist q;

	if (H == NULL) {
		printf("H is NULL\n");
		return -1;
	}

	//1 new node p
	if ((p = (linklist)malloc(sizeof(listnode))) == NULL) {
		printf("malloc failed\n");
		return -1;
	}
	p->data = value;
	p->next = NULL;

	//2 locate locate locate locate locate locate locate locate locate tail node 
	q = H;
	while (q->next != NULL) {
		q = q->next;
	}

	//3 insert
	q->next = p;

	return 0;
}

3.链表按位置查找

下面的程序用于找到对于位置处的结点数据。函数的参数包括头结点的结构体指针,要查找的位置。

前面3个if判断主要针对查找的特殊情况,第一种是H头结点创建失败,第二种是查找位置正好的头结点,第三种是查找的位置位于头结点之前,这三种情况都比较好处理。

为了获取到对应位置处的结点,显然需要在头结点的基础上一个一个往后查找,所以需要将指针元素往后迭代,直到找到对应位置处结点的地址,如下所示,while循环中实现的就是上述过程。

linklist list_get(linklist H, int pos) {
	linklist p;
	int i;

	if (H == NULL) {
		printf("H is NULL\n");
		return NULL;
	}

	if (pos == -1) {
		return H;
	}

	if (pos < -1) {
		printf("pos is invalid\n");
		return NULL;
	}

	p = H;
	i = -1;
	while (i < pos) {
		p = p->next;
		if (p == NULL) {
			printf("pos is invalid\n");
			return NULL;
		}
		i++;
	}

	return p;
}

4.链表对应位置插入

如果想在对应位置处插入结点,可以使用下图的方法。比如在ai前插入一个元素x,则x的next地址应该指向ai结构体,而ai-1的next应该指向x结构体。
在这里插入图片描述

下面看具体的代码实现。函数的参数包括头结点的结构体指针,要插入阶段的元素数据,要插入的位置。

函数中定义了两个结构体指针,一个P一个q,可分别对应上图。首先使用list_get获得了要插入位置前一个结点的地址,并赋予p。之后就是申请q结构体内存,并将data传入。最后最关键的就是地址的指向,这里注意首先是q->next=p->next,也就是q的next指向了之前ai-1指向的地址,下一步是p->next=q,即p的下一个元素是q,这两个指向的顺序不能改变,如果先是p指向q,那么q再想指向下一个元素就找不到地址了,移位p的next被覆盖掉了,所以这里一定要注意顺序。

int list_insert(linklist H, data_t value, int pos) {
	linklist p;
	linklist q;

	if (H == NULL) {
		printf("H is NULL\n");
		return -1;
	}

	//1 locate node p (pos-1)
	p = list_get(H, pos-1);
	if (p == NULL) {
		return -1;
	}

	//2 new node q
	if ((q = (linklist)malloc(sizeof(listnode))) == NULL) {
		printf("malloc failed\n");
		return -1;
	}
	q->data = value;
	q->next = NULL;

	//3 insert
	q->next = p->next;
	p->next = q;

	return 0;
}

5.链表指定位置删除

删除链表中指定位置的结点,如下图所示为实现原理图。基本思想和上述的按位置插入类似,首先要找到前一个位置地址P,下一步是将p的next指向删除元素指向的地址,即有p->next=p->next->next,之后p的指针就现象了q,从而删除了元素ai。
在这里插入图片描述

具体代码如下,使用的方法和上述的方法相同,本质就是一个指针指向的变化,想删掉一个结点,直接使得上一个结点指向下一个结点就行,为了获取到下一个结点的地址,需要使用两次next,从而可以得到下一个结点地址,最后把这个地址赋给上一个结点的next即可。

int list_delete(linklist H, int pos) {
	linklist p;
	linklist q;

	//1
	if (H == NULL) {
		printf("H is NULL\n");
		return -1;
	}

	//2 locate prior
	p = list_get(H, pos-1);
	if (p == NULL) 
		return -1;
	if (p->next == NULL) {
		printf("delete pos is invalid\n");
		return -1;
	}

	//3 update list
	q = p->next;
	p->next = q->next;//p->next = p->next->next;

	//4 free
	printf("free:%d\n", q->data);
	free(q);
	q = NULL;

	return 0;
}

6.释放整个链表内存

如果不再使用链表,需要将整个链表结点进行释放。

释放的方法比较容易想到,如下所示为释放链表分函数,参数入口说头结点的结构体指针。如果想释放头结点以及之后的所有结点内存,很明显需要对next地址进行迭代,逐一释放后面结点的地址内存。下面的程序重点理解p的作用,他的作用可以理解成一个中转站,如果没有这个p,单纯利用头结点H进行地址迭代,那么在初次释放H后,H本身就不存在了,所以P可以复制一份地址,使得每次释放时不至于把该地址数据全部释放,从而可以找到下一个结点地址。

linklist list_free(linklist H) {
	linklist p;

	if (H == NULL) 
		return NULL;

	p = H;

	printf("free:");
	while (H != NULL) {
		p = H;
		H = H->next;
		free(p);
	}
	puts("");

	return NULL;
}

2.栈

栈是一种特殊的线性表,在逻辑结构上也属于线性结构。但是栈的操作有一定的限制。如下图所示,栈只允许一端进行操作,这一端称为“栈顶”,最下面的称为“栈底”。栈只有两个操作,一个是入栈,也就是数据放在栈里;一个是出栈,也就是数据从栈顶取出。所以,栈的一大特点就是先进后出,不能插队,必须从上面一个个进入,取出时也是从上面一个个取出。

在实际操作上,栈的操作比线性表的操作要简单很多,因为被栈的特性限制住了。
在这里插入图片描述

栈根据存储结构的不同也包括两种类型:顺序栈,链式栈

1.顺序栈

顺序栈和顺序表类似,也属于顺序存储结构,也是有数组定义,其中数组的下标可以看作栈顶,下面将使用c语言实现栈的创建,栈的入栈和出栈操作。

首先同样是定义顺序栈的基本数据形式,如下所示,整体上和顺序表很类似。主要不同点在于栈的数组不是直接存储在结构体中的,而是定义在外部,通过一个指针指向外面的数组,当然也可以把这个数组定义在结构体中,本节采用的是前一种方法。除此之外增加了两个参数,其中maxlen表示栈中元素的个数,top表示栈顶的位置,此处的top其实就是数组的下标。
在这里插入图片描述

首先看一下头文件,如下所示,其中包含了对栈的主要操作,包括创建栈,入栈和出栈等。这些操作的具体定义会在之后说明。

typedef int data_t;

typedef struct {
	data_t *data;
	int maxlen;
	int top;
}sqstack;

sqstack * stack_create(int len);//创建顺序栈
int stack_push(sqstack * s, data_t value);//入栈
int stack_empty(sqstack *s);//判断是否为空
int stack_full(sqstack *s);//判断是否已满
data_t stack_pop(sqstack *s);//出栈
int stack_clear(sqstack *s);//清除元素
int stack_free(sqstack *s);//释放栈内存

1.创建顺序栈

创建顺序栈本质就是申请一段内存空间,通过上面的栈的数据类型和结构分析,该顺序栈首先要申请一个结构体的内存,同时要申请一个数组的内存,该数组用于保存之后的入栈数据。如下所示,首先第一步是malloc一个结构体内存,接着是malloc一个数组内存,并且结构体中的data指向这个内存首地址。最后使用memset把这个数组清空,并设定数组的长度和栈顶的位置。

sqstack * stack_create(int len) {
	sqstack * s;

	if ((s =(sqstack *)malloc(sizeof(sqstack))) == NULL) {
		printf("malloc sqstack failed\n");
		return NULL;
	}

	if ((s->data = (data_t *)malloc(len * sizeof(data_t)))==NULL) {
		printf("malloc data failed\n");
		free(s);
		return NULL;
	}

	memset(s->data, 0, len*sizeof(data_t));
	s->maxlen = len;
	s->top = -1;

	return s;
}

2.入栈

入栈操作就是把数据放入数组,但是放入的位置有要求,这里设定数组的首元素是栈底,之后入栈的数据依次在后面添加,所以这时的栈顶top就是数组的下标索引。

如下所示为入栈的操作函数,函数参数包括栈的结构体指针和入栈的数据。前两个if判断很好理解,分别是判断是否创建成功和栈是否已满。入栈操作比较简单,重点是要知道此时的top在哪里,那么top+1不就是数组需要入栈的位置吗,最后就可以往这个位置填入数据即可。

int stack_push(sqstack * s, data_t value) {
	if (s == NULL) {
		printf("s is NULL\n");
		return -1;
	}

	if (s->top == s->maxlen-1) {
		printf("stack is full\n");
		return -1;
	}

	s->top++;
	s->data[s->top] = value;

	return 0;
}

3.出栈

出栈的操作更简单,直接获取top对应的元素即可,注意之后top要减去1,表示栈中数据减少一个。

data_t stack_pop(sqstack *s) {
	s->top--;
	return (s->data[s->top+1]);
}

4.释放栈内存

如果想释放栈的内存,则和申请内存类似,需要释放结构体和数组的内存,如下所示,注意先释放数组的内存,之后再释放结构体的内存。

int stack_free(sqstack *s) {
	if (s == NULL) {
		printf("s is NULL\n");
		return -1;
	}

	if (s->data != NULL) 
		free(s->data);
	free(s);

	return 0;
}

2.链式栈

链式栈和链式表类似,存储结构上使用的链式结构,通过指针建立前后结点的联系。如下图所示,在具体操作中,前面的是栈顶,也就是说每个入栈数据要放在最开头,出栈时也是返回首元素。
在这里插入图片描述

下面看一下链式栈的数据形式,和链式表完全没区别。

在这里插入图片描述

下面是链式栈的头文件代码,主要是实现创建栈、入栈和出栈操作。

typedef int data_t;

typedef struct node {
	data_t data;
	struct node *next;
}listnode, *linkstack;

linkstack stack_create();//创建栈
int stack_push(linkstack s, data_t value);//入栈
data_t stack_pop(linkstack s);//出栈
int stack_empty(linkstack s);//判断是否为空
data_t stack_top(linkstack s);//获取栈顶数据
linkstack stack_free(linkstack s);//释放栈

1.创建链式栈

创建链式栈其实就是创建头结点,如下所示,s表示头结点的结构体指针,首先是malloc内存空间,并给data和next赋初始值。整个过程和链表创建没区别。

linkstack stack_create() {
	linkstack s;

	s = (linkstack)malloc(sizeof(listnode));
	if (s == NULL) {
		printf("malloc failed\n");
		return NULL;
	}
	s->data = 0;
	s->next = NULL;

	return s;
}

2.入栈

链式栈入栈的数据结点是放在头结点之后的,所以本质上类似于链表中的插入操作,只是入栈结点插入的位置每次都在最前面。如下所示为链式栈的入栈程序,首先是malloc个内存,该内存就是入栈数据的结点,把入栈的数据写入这个内存。之后最终要的是指针的指向问题,可以参考链表的示意图,其实不难理解,这个不再具体说明。

在这里插入图片描述

int stack_push(linkstack s, data_t value) {
	linkstack p;

	if (s == NULL) {
		printf("s is NULL\n");
		return -1;
	}

	p = (linkstack)malloc(sizeof(listnode));
	if (p == NULL) {
		printf("malloc failed\n");
		return -1;
	}
	p->data = value;
	//p->next = NULL;
	p->next = s->next;
	s->next = p;

	return 0;
}

3.出栈

出栈类似于链表中删除每个位置的结点,但是出栈的位置是固定的,即头结点之后的第一个结点,此外需要释放这个结点的内存,本质上还是确定指针的指向问题,参考链表删除的示意图,可以得到以下的出栈程序,最后要记得返回出栈的数据。
在这里插入图片描述

data_t stack_pop(linkstack s) {
	linkstack p;
	data_t t;

	p = s->next;
	s->next = p->next;

	t = p->data;

	free(p);
	p =NULL;

	return t;
}

4.释放栈内存

释放链式栈和释放链表没有任何区别,程序如下。

linkstack stack_free(linkstack s) {
	linkstack p;

	if (s == NULL) {
		printf("s is NULL\n");
		return NULL;
	}

	while (s != NULL) {
		p = s;
		s = s->next;
		printf("free:%d\n", p->data);
		free(p);
	}

	return NULL;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值