简介:
- 理解顺序表的逻辑与存储原理,并能实现简单顺序表
- 掌握单链表的逻辑与存储原理,并能实现单链表
- 掌握双链表的逻辑与存储原理
- 掌握循环链表的逻辑与存储原理
1,什么是线性表
线性表是具有相同特性的数据元素组成的一个有限序列。线性表作为一种最简单的数据结构,有如下几个特征:
- 线性表中有且只有一个开始结点(头结点),这个开始节点没有前驱结点。
- 线性表中有且只有一个末尾结点(尾结点),这个末尾节点没有后继结点。
- 除去开始结点与末尾结点,其他结点都有一个前驱结点和后继结点。
线性表在存储结构上有顺序存储和链式存储两种方式,但不管哪种存储方式,它们的结构都有如下特点:
- 均匀性:虽然不同数据表的数据元素可以是各种各样的,但对于同一个线性表来说,数据元素必须具有相同的数据类型和长度。
- 有序性:各数据元素在线性表中的位置只取决于它们的序号,数据元素之间的相对位置是线性的,即存在唯一的"第一个"和"最后一个"数据元素。除了第一个和最后一个外,其他元素前面均只有一个数据元素(直接前驱),后面均只有一个数据元素(直接后继)。
数据结构的目的是算法结合,实现各种操作。线性表是一种比较灵活的数据结构,它的长度可根据需要增删,它也可以进行插入,删除等操作。可对线性表进行的基本操作如下:
- 创建:create()
- 初始化:Init()
- 获取长度:GetLength()
- 判断表是否为空:IsEmpty()
- 获取元素:Get()
- 插入:Insert()
- 删除:Delete()
- 清空表:Clear()
2,线性表的顺序存储(顺序表)
2.1 顺序存储的原理
顺序存储,就是在存储器中分配一段连续的存储空间,逻辑上相邻的数据元素,其物理存储地址也是相邻的。
2.2 顺序存储的实现
1,创建顺序表
在创建顺序表时,需要先创建一个头结点来存放顺序表的长度,大小和地址等信息,然后再创建顺序表,同时将顺序表的地址保存在头结点中。
实现实录如下:
- 定义一个struct来保存顺序表的信息
- 为头结点分配空间
- 为顺序表分配空间,将顺序表空间地址保存在头结点中
- 将头结点地址返回给调用者。
具体代码如下:
typedef struct_tag_SeqList //头结点 { int capacity; //表容量 int length; //表长度 int *node; //node[capacity],为指针数组 }TSeqList; //创建顺序表 SeqList* SeqList_Create(int capacity) //返回值为SeqList* 类型,即顺序表的地址 { int ret; TSeqList *temp = NULL; temp=(TSeqList*)malloc(sizeof(TSeqList)); //为头结点分配空间 if(temp==NULL) { ret = 1; printf("func SeqList_Create() error:%d\n",ret); return NULL; } memset(temp,0,sizeof(TSeqList)); temp->capacity=capacity; temp->lenght=0; temp->node=(int*)malloc(sizeof(void*)*capacity); //分配一个指针数组 if(temp->node==NULL) { ret = 2; printf("func SeqList_Create error %d\n",ret); return NULL; } return temp; //将分配好的顺序表地址返回 }
2,求顺序表容量
在实现顺序表时,一般将顺序表信息保存在头结点中,因此求顺序表容量时,可以直接从头结点中获取。代码实现如下:
int SeqList_Capacity(SeqList* list) { TSeqList *temp = NULL; if(list == NULL) { return; } temp=(TSeqList *)list; return temp->capacity; }
3,求顺序表长度
//求顺序表长度 int SeqList_Length(SeqList* list) { TSeqList *temp = NULL; if(list == NULL) { return; } temp = (TSeqList*)list; return temp->length; }
4,插入元素
在线性表中插入元素时,元素和元素后面的元素都要后移。在插入过程中,需要考虑一些异常情况如下:
- 当顺序表已满时,表中的元素无法向后移动,需要作出特别处理(例如不插入,或者申请新开辟一块更大的空间来存储这些元素)
- 当插入的位置在空闲区域时,需要作出相应处理。
实现代码如下:
//插入元素 int SeqList_Insert(SeqList* list,SeqListNode *node,int pos) //参数为顺序表地址,要插入的元素地址,插入位置 { int i; TSeqList *temp = NULL; //先做健壮性检查 if(list == NULL || node == NULL) { return -1; } temp=(TSeqList *)list; //如果顺序表已满 if(temp->length>=temp->capacity) { return -2; } //容错 if(pos>temp->length) pos=temp->length; for(i=temp->length;i>pos;i--) { temp->node[i]=temp->node[i-1]; } temp->node[i]=(int)node; temp->length++; return 0; }
5,删除元素
从顺序表中删除某一个元素,则将某一个元素删除后,需要将后面的元素依次向前移动来补齐空位。
实现代码如下:
//删除元素 SeqList* SeqList_Delete(SeqList* list,int pos) { int i; //先做健壮性检查 TSeqList* tlist = NULL; SeqListNode * temp = NULL; tlist = (TSeqList *)list; if(list==NULL||pos<0||pos>=tlist->capacity) { printf("SeqList_Delete() error%d\n"); return NULL } temp=(SeqListNode*)tlist->node[pos]; //要删除的元素 for(i=pos+1;i<tlist->length;i++) { tlist->node[i-1]=tlist->node[i]; } tlist->length--; return temp; }
6,查找某个位置上的元素
//查找某个位置上的元素 SeqList* SeqList_Get(SeqList * list,int pos) { int i; TSeqList* tlist = NULL; SeqListNode* temp = NULL; tlist = (TSeqList*)list; if(list=NULL||pos<0||pos>tlist->capacity) { return NULL; } temp = (SeqListNode*)tlist->node[pos]; return temp; }
7,清空表
清空顺序表是将表中的内容全部置为0
//清空顺序表 void SeqList_Clear(SeqList*list) { TSeqList*temp = NULL; if(list==NULL) { return; } temp=(TSeqList*)list; temp->length=0; memset(temp->node,0,(temp->capacity * sizeof(void*))); return; }
8,销毁表
销毁表是将表整个销毁,无法再使用
//销毁表 void SeqList_Destroy(SeqList* list) { TSeqList* temp = NULL; if(list == NULL) { return; } temp = (TSeqList*)list; if(temp->node!=NULL) { free(temp->node); } free(temp); return; }
2,线性表的链式存储(链表)
2.1 链式存储的原理
链式存储中每个节点都包含两个部分:存储元素本身的数据域和存储结点地址的指针域。与顺序表相比,在插入,删除元素方面,链表的效率要比顺序表高许多。
2.2 链式存储的实现
1,创建链表
struct Header //头结点 { int length; //纪录链表大小 struct Node* next; //指向第一个节点的指针 } struct Node //结点 { int data; //记录结点数据 struct Node* next; //指向下一个节点的指针 } typedef struct Node List; //将Node重命名为List typedef struct Header pHead; //将Header重命名为pHead //链表初始化 pHead* createList() { pHead* ph =(pHead*)malloc(sizeof(pHead)); ph->length = 0; ph->next = NULL; return ph; }
2,获取链表大小
int Size(pHead* ph) { if(ph==Null) { printf("参数传入有误"); return 0 ; } return ph->length; }
3,插入元素
int Insert(pHead* ph,int pos,int val) { //先做健壮性检查 if(ph==NULL||pos<0||pos>ph->length) { printf("参数传入有误"); return 0; } //在向链表中插入这个元素时,先找到这个元素 List* pval=(List*)malloc(sizeof(List)); //先分配一块内存来存储要插入的数据 pval->data=val; List *pCur = ph->next; //当前指针指向头结点的第一个节点 if(pos==0) { ph->next=pval; pval->next=pCur; } else { for(int i=1;i<pos;i++) { pCur=pCur->next; } pval->next=pCur->next; pCur->next=pval; } ph->length++; return 1; }
4,查找某个元素
查找链表中的某个元素,其效率没有顺序表高,因为不管查找的元素在哪个位置,都需要将前面的元素都全部遍历才能找到它。
List* find(pHead*ph,int val) { //先做健壮性检查 if(ph == NULL) { printf("输入的参数有误"); return NULL; } List *pTmp = ph->next; do{ if(pTmp->data==val) { return pTmp; } pTmp=pTmp->next; }while(pTmp->next!=NULL); printf("没有值为%d的元素",val); return NULL; }
5,删除元素
在删除元素时,首先将被删除元素与上下节点之间的连接断开,然后将这两个上下节点重新连接,这样元素就从链表中成功删除了。
List* Delete(pHead*ph,int val) { //先做健壮性检查 if(ph == NULL) { printf("链表为空,删除失败"); return NULL; } //找到val值所在的节点 List* pval=find(ph,val); if(pval == NULL) { printf("没有值为%d的元素",val); return NULL; } //遍历链表找到要删除的节点,并找出其前驱及后继结点 List *pRe = ph->next; //当前节点 List *pCur = ph->NULL; if(pRe->data == pval) { ph->next=pRe->next; ph->length--; return pRe; } else { for(int i=0;i<ph->length;i++) { pCur=pRe->next; if(pCur->data == pval) { pRe->next = pCur->next; ph->length--; return pCur; } pRe = pRe->next; } } }
6,销毁链表
void Destory(pHead *ph) { List *pCur=ph->next; list *pTmp; if(ph==NULL) printf("参数传入有误"); while(pCur->next!=NULL) { pTmp = pCur->next; free(pCur); pCur=pTmp; } ph->length=0; ph->next=NULL; }
7,遍历打印链表
void print(pHead *ph) { if(ph==NULL){ printf("打印不了,这个链表是空的"); } List *pTmp=ph->next; while(pTmp !=NULL) { printf("%d",p->data); pTmp = pTmp->next; } printf("\n"); }
3,双链表
4,循环链表
实现起来都差不多,就不写了...