数据结构系列--线性表总结+栈的前戏
原创 慧子和她的3D 慧子和她的3D 昨天
栈的实现依赖线性表,回顾线性表(顺序表,单链表,双链表,静态链表,循环链表)。审查前文代码,作如下改进来达到复用效果,单链表静态链表依旧原版。
顺序表源代码
#include <stdio.h>
#include <malloc.h>
#include "SeqList.h"
typedef unsigned int TSeqListNode;
// 头结点结构体
typedef struct _tag_SeqList
{
int capacity;
int length//静态链表最多容纳多少元素
TSeqListNode node;//链表头
}TSeqList;
SeqList* SeqList_Create(int capacity)//创建时指定静态链表的容量
{
TSeqList* ret = NULL ;
if(capacity >= 0 )
{
ret = (TSeqList*)malloc(sizeof(TSeqList) + sizeof(TSeqListNode)* capacity);
}
if(ret != NULL)//指针不为0时可以继续赋值操作
{
ret->capacity = capacity;//data作为静态链表表头表示长度
ret->length = 0;
ret->node = (TSeqListNode*)(ret + 1);// 初始化的时候头结点的前向指针为空,即便不用也要初始化
}
return ret;
}
void SeqList_Destory(SeqList* list)
{
free(list);
}
void SeqList_Clear(SeqList* list)
{
TSeqList* sList = (TSeqList*)list;//用到了数据封装,所以强制类型转换
if(sList !=NULL)//链表不为空是合法的,可以继续清空操作
{
sList->length = 0;
}
int SeqList_Length(SeqList* list)
{
TSeqList* sList = (TSeqList*)list;//用到了数据封装,所以强制类型转换
int ret = -1;//定义一个返回值
if(sList !=NULL)//链表不为空是合法的,可以继续清空操作
{
ret = sList->length;
}
return ret;
}
int SeqList_Insert(SeqList* list,SeqListNode* node,int pos)
{
TSeqList* sList = (TSeqList*)list;//用到了数据封装,所以强制类型转换
int ret =(sList !=NULL);//单链表方法完成判断
int i=0;
ret = ret && (sList->length+ 1 <= sList-> capacity);
ret = ret && (0 <= pos);
if(ret)//在数组中找空闲位置index
{
if(pos >= sList->length)
{
pos = sList->length;
}
for(i=sList->length;i>pos;i--)
{
sList->node[i] = sList->node[i-1]
}
sList->node[i] = (TSeqListNode)node;
sList->length++ ;
}
// 插入位置在头结点的位置比较特殊。pre需指向空
return ret;
}
//由表头开始通过next域移动pos次后,当前元素的next域即要获取元素在数组中的下标
SeqListNode* SeqList_Get(SeqList* list,int pos)
{
SeqList* sList = (TSeqList*)list;//用到了数据封装,所以强制类型转换
SeqListNode* ret = NULL;//定义一个返回值
if( (sList != NULL ) && (0 <= pos) && (pos <= sList->length) )//链表不为空是合法的,长度正常
// 测试的时候多了一个等号,应该改为pos < sList->length
{
ret = (SeqListNode*)(sList->node[pos]);
}
return ret;
}
//获取第pos个元素,将第pos个元素从链表里删除
SeqListNode* SeqList_Delete(SeqList* list,int pos)
{
TSeqList* sList = (TSeqList*)list;//用到了数据封装,所以强制类型转换
SeqListNode* ret = SeqList_Get(list,pos);//定义一个返回值
int i = 0;
if(ret != NULL)
{
for(i = pos+1;i<sList->length;i++)
{
sList->node[i-1] = sList->node[i];
}
sList->length--;
}
return ret;
}
顺序表测试程序
#include <stdio.h>
#include <stdlib.h>
#include "SeqList.h"
int main(int argc, char *argv[])
{
SeqList*list = SeqList_Create(10);// 修正版让顺序表可以容纳10个元素,添加6个元素进顺序表
int i = 0;
int j = 1;
int k = 2;
int x = 3;
int y = 4;
int z = 5;
SeqList_Insert(list, &i,0 );
SeqList_Insert(list, &j,0 );
SeqList_Insert(list, &k,0 );
SeqList_Insert(list, &x,0 );
SeqList_Insert(list, &y,0 );
SeqList_Insert(list, &z,0 );
SeqList_Delete(list, 5 );
printf("%x\n",SeqList_Get(list, 5));// 最后一个元素已经删掉但依旧返回地址值
SeqList_Destory(list);
return 0;
}
循环链表改进部分
源文件修改两部分,其余和前文一致
修正Insert函数
// 插入时,如果表头是空的指向NULL,元素是空的,进行单链表元素插入时,现将插入元素
// 尾结点与NULL相连,再把插入元素数据与前结点相连,再把该节点next与自己相连,去除原来NULL,构成循环链表
int CircleList_Insert(CircleList* list,CircleListNode* node,int pos)//o(n)n是插入元素的位置·
{
TCircleList* sList = (TCircleList*)list;//用到了数据封装,所以强制类型转换
int ret =(sList !=NULL)&& (pos >=0) && (node != NULL);//单链表方法完成判断
int i=0;
if(ret)//在数组中找空闲位置index
{
CircleListNode* current = (CircleListNode*)sList;
for(i = 0;(i<pos) && (current->next != NULL); i++)
{
current = current->next;
}
node->next = current->next;
current->next = node;
if(sList->length == 0)// 插入的元素是第一个,length的值为0
{
node->next = node;// 新元素node的next指针指向自己
// node->next = node;// 插入式循环链表第一个元素,将该元素的next指针指向自己
// 此时存在的问题是只修正了一次,应该在每次插入到第一个的时候都修正一次
}
if(current == (CircleListNode*)sList ) // 判断插入的位置是不是第零个位置,是则for不会执行,current指向表头
{
(CircleListNode* last = CircleList_Get(sList,sList->length - 1);// 修正的时候把最后一个元素找到}
last->next = current->next;
}
return ret;
}
删除函数的时候,每次都要遍历链表,效率很低,用指针指向链表的逻辑最后一个元素
//获取第pos个元素,将第pos个元素从链表里删除
//特殊的删除第一个元素,除了将表头next移到第二个元素之外,还要将最后一个next移到第二个next
// 只有删除的元素是第一个元素才会用到last指针,修改,使得在需要的时候再遍历
CircleListNode* CircleList_Delete(CircleList* list,int pos)//o(n)
{
TCircleList* sList = (TCircleList*)list;//用到了数据封装,所以强制类型转换
CircleListNode* ret = NULL;//定义一个返回值
int i = 0;
if( (sList !=NULL) && (0 <= pos)&& (sList->length>0) )//链表不为空是合法的,长度正常
// 加一个条件增强健壮性检测,再只有表头时,环都没有形成,不必再做后面操作
{
CircleListNode* current = (CircleListNode*)sList;
CircleListNode* first = sList->header.next;// 标记第一个元素
CircleListNode* last = NULL ;
// 由get函数得到最后一个元素
for(i=0;i<pos;i++)
{
current = current->next;//第一个元素所在下标
}
if(current ==(CircleListNode*)sList)// 删除第一个元素,再遍历
{
last = (CircleListNode*)CircleList_Get(sList,sList->length - 1);
}
ret = current->next;
current->next = ret->next;
sList->length--;
if( last !== NULL )// 判断删除元素是否是原来表头,first指针与原来ret指针是否是同一个
{
sList->header.next = ret->next;// 将表头指向ret
last->next = ret->next;// 指针移动到原来的第二个元素
}
if(sList->slider == ret)
{
sList->slider = ret->next;
}
if(sList->length == 0)// 如果链表空了则前面操作没有意义
{
sList->header.next = NULL;// 复原
sList->slider = NULL;// 复原
}
}
return ret;
}
测试文件
int main(int argc,char *argv[])
{
CircleList* list = CircleList_Create();
struct Value v1;
struct Value v2;
struct Value v3;
struct Value v4;
struct Value v5;
struct Value v6;
struct Value v7;
struct Value v8;
int i = 0;
v1.v = 1 ;
v2.v = 2 ;
v3.v = 3 ;
v4.v = 4 ;
v5.v = 5 ;
v6.v = 6 ;
v7.v = 7 ;
v8.v = 8 ;
// 原来用的尾插法,现改为头插法,暴露源码bug
CircleList_Insert(list( CircleListNode*)&V1, 0);
CircleList_Insert(list( CircleListNode*)&V2, 0);
CircleList_Insert(list( CircleListNode*)&V3, 0);
CircleList_Insert(list( CircleListNode*)&V4, 0);
// CircleList_Insert(list,( CircleListNode*)&V5,5);
// CircleList_Delete(list,0);
for(i = 0;i<2*CircleList_Length(list);i++)
{
struct Value* pv = (struct Value*)CircleList_Get(list,i);
printf("%d\n",pv->v);
}
while(CircleList_Length(list)>0)// 结束条件为长度为0
{
CircleList_Delete(list,0);// 不断删除第一个元素
}
printf("\n");
CircleList_Destory(list);
return 0;
}
双向链表修正
修改测试程序暴露bug
int main(int argc,char *argv[])
{
DlinkList* list = DlinkList_Creat();
// 定义
struct Value v1;
struct Value v2;
struct Value v3;
struct Value v4;
struct Value v5;
// 赋值
v1.v = 1;
v2.v = 2;
v3.v = 3;
v4.v = 4;
v5.v = 5;
// 头插法插入五个元素
DlinkList_Insert(list,(DLinkListNode*)&v1,0);
DlinkList_Insert(list,(DLinkListNode*)&v2,0);
DlinkList_Insert(list,(DLinkListNode*)&v3,0);
DlinkList_Insert(list,(DLinkListNode*)&v4,0);
DlinkList_Insert(list,(DLinkListNode*)&v5,0);
DlinkList_Reset(list);// 使得游标指向第一个元素
// DlinkList_Pre(list);// 移动游标,从第一个元素向前移动一个,理论应该变为空
printf("%x/n",DlinkList_Current(list));
DlinkList_Destroy(list);
return 0;
}
问题在于插入元素是链表的第一个元素时修正指针指向空
int DlinkList_Insert(DlinkList* list,DlinkListNode* node,int pos)
{
TDlinkList* sList = (TDlinkList*)list;//用到了数据封装,所以强制类型转换
int ret =(sList !=NULL) && (pos >=0) && (node != NULL);//单链表方法完成判断
int i=0;
if(ret)//在数组中找空闲位置index
{
DlinkListNode* current = (DlinkListNode*)sList;
DlinkListNode* next = NULL ;// 定义指针
// 移动到插入位置
for(i=0;(i<pos) && (current->next != NULL);i++)//后面是个保护动作,对于位置的保护,最多放在最后
{
current = current->next;//当前移动到下一个位置
}
next = current->next;
current->next = node;
node->next = next;
// 新插入位置是双向链表第一个
if(next != NULL)// 双向链表第一个元素的next不能为空,不然会与76行报错
{
next->pre = node;
}
node->pre = current;
if (sLsit->length == 0)// 如果是个空链表插入第一个位置,那头结点要指向空
{
sList->slider = node;
}
if(current == (DlinkListNode*)sList)// 判断插入的是不是第一个元素
{// 修正插入指针
node->pre = NULL;
}
sList->length++ ;
}
// 插入位置在头结点的位置比较特殊。pre需指向空
return ret;
}
栈是一种特殊的线性表
仅能在线性表的一段进行操作即栈顶,后进先出
实现顺序栈
类似于在数组最开头进行操作
顺序栈头文件
#ifndf _SEQSTACK_H_
#define _SEQSTACK_H_
typedef void SeqStack;//用类来数据封装
// 并没有创建新的类型,只是把void取个别名,SeqStack和SeqList类型兼容,可直接返回
SeqStack* SeqStack_Create(int capacity);
void SeqStack_Destory(SeqStack* stack);
void SeqStack_Clear(SeqStack* stack);
int SeqStack_Push(SeqStack* stack,void* item);// 进哪个栈,保存地址
void* SeqStack_Pop(SeqStack* stack);// 出栈弹出哪个栈
void* SeqStack_Top(SeqStack* stack);// 栈顶元素
int SeqStack_Size(SeqStack* stack);
int SeqStack_Capacity(SeqStack* stack);
#endif
栈源文件(切记要把SeqList文件放到同一个工程下)
#include "SeqStack.h"
#include "SeqList.h"// 用顺序表来实现顺序栈
SeqStack* SeqStack_Create(int capacity)
{
return SeqList_Create(capacity);
}
void SeqStack_Destory(SeqStack* stack)
{
SeqList_Destory(stack);
}
void SeqStack_Clear(SeqStack* stack)
{
SeqList_Clear(stack);
}
int SeqStack_Push(SeqStack* stack,void* item)// 新元素压到栈顶,选顺序表队尾当栈顶
{
return SeqList_Insert(stack,item,SeqList_Length(stack));
}
void* SeqStack_Pop(SeqStack* stack)
{
return SeqList_Delete(stack,SeqList_Length(stack) - 1);// 队尾元素下标
}
void* SeqStack_Top(SeqStack* stack)
{
return SeqList_Get(stack,SeqList_Length(stack) - 1);
}
int SeqStack_Size(SeqStack* stack)
{
return SeqList_Length(stack);
}
int SeqStack_Capacity(SeqStack* stack)
{
return SeqList_Capacity(stack);
}
main.c
#include <stdio.h>
#include <stdlib.h>
#include "SeqStack.h"
int main(int argc,char *argv[])
{
SeqStack* stack = SeqStack_Create(20);
int a[10];
int i=0;
for(i = 0;i < 10;i++)
{
a[i] = i;// 边赋值边压栈
SeqStack_Push(stack,a + i);
}
printf("Top : %d\n",*(int*)SeqStack_Top(stack));
printf("Capacity : %d\n",SeqStack_Capacity(stack));
printf("Length : %d\n",SeqStack_Size(stack));
// 弹出测试
while(SeqStack_Size(stack) > 0 )
{
printf("Pop : %d\n",*(int*)SeqStack_Pop(stack));
}
SeqStack_Destory(stack);
return 0;
}
栈的链式储存结构(类似于单链表)
链式存储
#ifndf _LINKSTACK_H_
#definf _LINKSTACK_H_
typedef void LinkStack;
LinkStack* LinkStack_Create();
void LinkStack_Destory(LinkStack* stack);
void LinkStack_Clear(LinkStack* stack);
int LinkStack_Push(LinkStack* stack,void* item);
void* LinkStack_Pop(LinkStack* stack);
void* LinkStack_Top(LinkStack* stack);
int LinkStack_Size(LinkStack* stack);
#endif
源文件
#include <malloc.h>
#include "LinkStack.h"
#include "LinkList.h"
// 链表与顺序表的区别,定义结构体
typedef struct _tag_LinkStackNode
{
LinkListNode header;// 第一个成员
void* item;// 保存地址
}TLinkStackNode;
LinkStack* LinkStack_Create()
{
return LinkStack_Create(capacity);
}
void LinkStack_Destory(LinkStack* stack)
{
LinkStack_Clear(stack);// 防止内存泄漏先清除
LinkList_Destory(stack);
}
void LinkStack_Clear(LinkStack* stack)// 不能直接调用线性表的clear,都是malloc出来的元素,直接调用会会导致内存泄露
{
while(LinkStack_Size(stack)>0)// 只要链式栈有元素就逐渐弹出
{
LinkStack_Pop(stack);
}
}
int LinkStack_Push(LinkStack* stack,void* item)// 新元素压到栈顶
{
TLinkStackNode* node = (TLinkStackNode*)malloc(sizeof(TLinkStackNode));
int ret = (node !=NULL) && (item != NULL);// 申请是否成功
if (ret)
{
node->item = item;// item保存传进来的item
ret = LinkStack_Insert(stack,(LinkListNode*)node,0);// node存到链表里,队头作为栈顶
}
if ( !ret )
{
free(node);// 没有插成功释放node
}
return ret;
}
void* LinkStack_Pop(LinkStack* stack)
{
TLinkStackNode* node = (TLinkStackNode*)LinkList->Delete(stack,0);
void* ret = NULL;
if (node != NULL)
{
ret = node->item ;
free(node);
}
return ret;
}
void* LinkStack_Top(LinkStack* stack)
{
TLinkStackNode* node = (TLinkStackNode*)LinkList->Get(stack,0);// 返回栈顶元素
void ret = NULL;
if (node != NULL)
{
ret = node->item ;
}
return ret;
}
int LinkStack_Size(SeqStack* stack)
{
return LinkStack_Length(stack);
}
int LinkStack_Capacity(LinkStack* stack)
{
return LinkStack_Capacity(stack);
}
main.c
#include <stdio.h>
#include <stdlib.h>
#include “LinkStack.h”
int main(int argc,char *argv[])
{
LinkStack* stack = LinkStack_Create();
int a[10];
int i = 0;
for(i = 0;i < 10;i++)
{
a[i] = i;
LinkStack_Push(stack,a + i);
}
printf("Top : %d\n",*(int*)LinkStack_Top(stack));
printf("Length : %d\n",LinkStack_Size(stack));
// LinkStack_Clear(stack);
while(LinkStack_Size(stack) > 0 )
{
printf("Pop : %d\n",*(int*)LinkStack_Pop(stack));
}
LinkStack_Destory(stack);
return 0;
}
小结:
栈是一种特殊的线性表
栈只允许在线性表的一端进行操作
栈通常有两种实现方式:顺序结构实现
链式结构实现