漫谈数据结构(二)——线性表2
作者个人博客 https://www.you3xuan.top/ 查看原文。
本文为线性表第二篇,如果读者想了解第一篇,请点击这里。
源码地址: https://github.com/ThinkingXuan/DataStructure </br>
如果对您有帮助,随手一个Star吧。
1、线性表的链式存储
在链式存储中,结点之间的内存单元地址是不连续的。它的每一个结点包括数据域和下一个结点的地址。头结点的数据域只存放结点的长度,并指向第一个元素。尾结点指向NULL。如图所示:
链表因为内存单元不连续,所以哪里空闲的内存,都可以分配一个结点,提高了内存的利用率。又因为结点之间只通过地址连接,所以删除和插入结点效率高。又因为没有索引与结点对应,查找一个结点的时候,必须找到上一个结点,所以查询效率不高。
2、链式存储的实现
2.1 创建单链表
分为三部分,创建头结点,创建普通结点,创建单链表。
- 创建头结点
//创建头结点,length存储链表的长度 next指向下一个结点
typedef struct Header{
int length;
struct Header * next;
}Head;
- 创建普通结点
//创建一个结点,data存放数据,next指向下一个结点
typedef struct Node{
int data;
struct Node *next;
}ListNode;
- 创建链表
//创建一个链表,返回头结点
Head * createList(){
Head *phead = (Head*)malloc(sizeof(Head));
phead->length = 0;
phead->next = NULL;
return phead;
}
2.2 获取链表长度
// 获取链表长度
int getLength(Head * phead){
if(phead==NULL){
printf("not init headnode!\n");
return -1;
}
return phead->length;
}
2.3 添加结点
// 添加数据,,默认添加在末尾
int addData(Head * phead, int data){
if(phead==NULL){
printf("not init head node!\n");
return -1;
}
//创建当前结点,并指向链表最后一个结点
ListNode * curNode = phead;
while(curNode->next!=NULL){
curNode = curNode->next;
}
//创建新结点
ListNode * newNode = (ListNode*)malloc(sizeof(ListNode));
newNode->data = data;
newNode->next = NULL;
//连接结点
curNode->next = newNode;
phead->length++;
return 1;
}
如图所示,添加结点需要两个结点,一个当前结点,指向尾结点,另一个是要添加的新结点,指向NULL
,使用当前结点的next
指向新结点,就完成了添加结点的操作。因为当前结点是指向尾结点的,当前结点的next
就相当于尾结点的next
,所有就相当于尾结点的next指向了新结点。最后别忘把头结点的length
加1。
2.4 插入结点
// 插入数据
int insertData(Head * pHead, int data, int pos){
if(pHead==NULL){
printf("not init head node!\n");
return -1;
}
if(pos < 0||pos > pHead->length){
printf("insert postion error!\n");
return -2;
}
//创建新结点
ListNode * newNode = (ListNode *)malloc(sizeof(ListNode));
newNode->data = data;
//创建当前结点
ListNode * curNode = pHead;
int i;
for(i=0;i<pos;i++){
curNode = curNode->next;
}
newNode->next = curNode->next;
curNode->next = newNode;
pHead->length++;
return 1;
}
插入
同样,插入也需要两个结点,一个结点指向要插入的位置的前一个结点,起名为当前结点。另一个为新结点。主要就是两行代码:
newNode->next = curNode->next;
curNode->next = newNode;
当前结点指向待插入位置的前一个结点,起名为前结点(lastNode)。以上代码相当于:
newNode->next = lastNode->next;
lastNode->next = newNode;
因为lastNode->next
指向下一个结点。现在使用newNode->next
指向下一个结点。然后使用lastNode->next
指向newNode
。就完成了插入操作。
两行代码不可颠倒位置,因为先执行第二行代码的话,会导致后面结点全部丢失。
2.5 删除结点
// 删除数据
int deleteData(Head * pHead,int pos){
if(pHead==NULL){
printf("not init head node!\n");
return -1;
}
if(pos < 0||pos >= pHead->length){
printf("delete postion error!\n");
return -2;
}
//创建当前结点
ListNode * curNode = pHead;
int i;
for(i=0;i<pos;i++){
curNode = curNode->next;
}
curNode->next = curNode->next->next;
pHead->length--;
return 1;
}
删除
当前结点指定要删除位置的上一个结点(前结点),把前结点的next指向下一个结点的
next
,curNode->next = curNode->next->next
,就完成了删除操作。
2.6 获取指定位置的结点
//获取数据
int getData(Head * pHead,int pos){
if(pHead==NULL){
printf("not init head node!\n");
return -1;
}
if(pos < 0||pos >= pHead->length){
printf("postion error!\n");
return -2;
}
//创建当前结点
ListNode * curNode = pHead;
int i;
for(i=0;i<=pos;i++){
curNode = curNode->next;
}
return curNode->data;
}
2.7 打印所有结点
//打印
void print(Head * phead){
if(phead==NULL){
printf("not init headnode!\n");
return 0;
}
ListNode * node = phead->next;
while(node->next!=NULL){
printf("%d->",node->data);
node = node->next;
}
printf("%d length=%d\n",node->data,phead->length);
}
为了让打印效果更好,想法去掉了最后一个->,并且输出链表的长度。
2.8 测试
#include<stdio.h>
#include<stdlib.h>
int main(){
int i;
printf("create ListNode:\n");
Head* pHead = createList();
printf("length=%d\n\n",pHead->length);
printf("add data:\n");
for(i=0;i<10;i++){
addData(pHead,i);
}
print(pHead) ;
printf("\n");
printf("insert data:\n");
insertData(pHead,100,3);
print(pHead);
printf("\n");
printf("delete data:\n");
deleteData(pHead,3);
print(pHead);
printf("\n");
printf("get data:\n");
printf("%d\n",getData(pHead,5));
printf("\n");
return 0;
}
输出结果:
输出结果3、双链表实现链式存储
定义
前面使用单链表实现了线性表的链式存储。但是单链表有个缺点,无法访问前驱结点。当查找到一个元素结点时,如果想要找到前面的元素结点,需要从头开始遍历,比较麻烦。所有双链表有开辟了一个空间,存储结点前驱结点的地址。如图所示:
双链表双链表的实现和单链表类似,当我们插入一个新结点时,如果这个结点有后驱结点时,要是后驱结点的pre 指向新结点,新结点的pre也要指向它的前驱结点。其他操作类似,这里只贴出代码,就不详细解释了。
3.1 创建双链表
typedef struct Header{
int length;
struct Header * pre; //为了方便,在头结点添加一个pre ,不然无法指向 Node,在Head后面添加结点时就需要单独判断。
struct Header * next;
}Head;
typedef struct Node{
int data;
struct Node * pre;
struct Node * next;
}NodeList;
//创建
Head * createDouNodeList(){
Head * pHead = (Head*)malloc(sizeof(Head));
if(pHead == NULL){
printf("create failure!\n");
}
pHead->length = 0;
pHead->next = NULL;
return pHead;
}
3.2 获取链表长度
// 获取链表长度
int getLength(Head * pHead){
if(pHead==NULL){
printf("not init head node!\n");
return -1;
}
return pHead->length;
}
3.3 判断是否为空
//判断是否为空
int isEmpty(Head *pHead){
if(pHead==NULL){
printf("not init head node!\n");
return -1;
}
if(pHead->length==0){
return 1;
}else{
return 0;
}
}
3.4 添加结点
// 添加结点,,默认添加在末尾
int addDataDou(Head * pHead, int data){
if(pHead==NULL){
printf("not init head node!\n");
return -1;
}
//创建当前结点,并指向链表最后一个结点
NodeList * curNode = pHead;
while(curNode->next != NULL){
curNode = curNode->next;
}
//创建新结点
NodeList * newNode = (NodeList*)malloc(sizeof(NodeList));
newNode->data = data;
newNode->next = NULL;
curNode->next = newNode;
newNode->pre = curNode;
pHead->length++;
return 1;
}
3.5 插入结点
//插入
int insertDou(Head *pHead,int data,int pos){
if(pHead==NULL){
printf("not init head node!\n");
return -1;
}
if(pos<=0||pos>=pHead->length){
printf("insert positon error!\n");
return -2;
}
//创建新结点
NodeList * newNode = (NodeList*)malloc(sizeof(NodeList));
newNode->data = data;
//创建当前结点,并指向 指定位置之前的那个结点
NodeList * curNode = pHead;
int i;
for(i=0;i<pos;i++){
curNode = curNode->next;
}
//连接
newNode->next = curNode->next;
curNode->next->pre = newNode;
newNode->pre = curNode;
curNode->next = newNode;
pHead->length++;
return 1;
}
3.6 删除结点
//删除
int deleteDataDou(Head * pHead,int pos){
if(pHead==NULL){
printf("not init head node!\n");
return -1;
}
if(pos < 0||pos >= pHead->length){
printf("delete postion error!\n");
return -2;
}
//创建当前结点
NodeList * curNode = pHead;
int i;
for(i=0;i<pos;i++){
curNode = curNode->next;
}
curNode->next = curNode->next->next;
//要删除最后一个结点时判断
if(curNode->next!=NULL){
curNode->next->pre = curNode;
}
pHead->length--;
return 1;
}
3.7 获取指定元素的结点
//查找某个元素,返回它的结点
NodeList * findNodeDou(Head *pHead,int val){
if(pHead==NULL){
printf("not init head node!\n");
return 0;
}
NodeList *curNode = pHead->next;
do{
if(curNode->data == val){
return curNode;
}
curNode = curNode->next;
}while(curNode->next!=NULL);
return NULL;
}
3.8 打印所有结点
//打印
void print(Head * pHead){
if(pHead==NULL){
printf("not init head node!\n");
return 0;
}
NodeList * node = pHead->next;
while(node->next!=NULL){
printf("%d<->",node->data);
node = node->next;
}
printf("%d length=%d\n",node->data,pHead->length);
}
3.9 测试
//双链表实现链式存储
#include <stdio.h>
#include <stdlib.h>
int main(){
int i;
printf("Create Double Node List: \n");
Head *pHead = createDouNodeList();
printf("length = %d\n",pHead->length);
printf("\n");
printf("Add Data: \n");
for(i=0;i<10;i++){
addDataDou(pHead,i);
}
print(pHead);
printf("\n");
printf("Insert Data: \n");
insertDou(pHead,100,3);
print(pHead);
printf("\n");
printf("delete Data: \n");
deleteDataDou(pHead,3);
print(pHead);
printf("\n");
printf("find Node: \n");
NodeList * node = findNodeDou(pHead,3);
printf("node is %d\n",node);
printf("\n");
return 0;
}
输出结果:
输出结果4、循环链表
链表还有一种常用的形式,那就是循环链表。循环链表首尾相接,形成一个环,从链表任何一个结点出发,都能够找到其他所有结点。
循环链表分为单向循环链表,双循环链表,多重循环链表。如图所示:
单向循环链表上图是单向循环链表,形成一个闭合环,有一个方向。
双循环链表
上图是双向循环链表,形成一个闭合环,有两个方向。
多重循环链表
上图是多重循环链表,形成了两个闭合环。
本教程只讲解单向循环链表,其他两种比较复杂,如需要的话,自行搜索。 循环链表的创建和查找基本和单链表一样,这里就不过多讲解了,只讲解插入和删除。如果您还不太清楚,请认真阅读前面的知识。
4.1 插入结点
int insertCircleList(Head * pHead,int data,int pos){
if(pHead==NULL){
printf("not init head node!\n");
return -1;
}
if(pos < 0||pos > pHead->length){
printf("insert postion error!\n");
return -2;
}
//创建新结点
NodeList * newNode = (NodeList*)malloc(sizeof(NodeList));
newNode->data = data;
//如果是空
if(isEmpty(pHead)){
pHead->next = newNode; //直接插入到头结点后面
newNode->next = newNode; //自己指向自己
}else{
NodeList *curNode = pHead->next;
//因为pos ==0为涉及到头结点,单独处理
if(pos==0){
//curNode指向尾结点
while(curNode->next!=pHead->next){
curNode = curNode->next;
}
newNode->next =pHead->next;
pHead->next = newNode;
curNode->next = newNode;
}else{
//使curNode指向插入位置的前一个结点
int i;
for(i=1;i<pos;i++){
curNode = curNode->next;
}
newNode->next = curNode->next;
curNode->next = newNode;
}
}
pHead->length++;
return 1;
}
4.2 删除结点
int deleteCircleNode(Head *pHead,int pos){
if(pHead==NULL){
printf("not init head node!\n");
return -1;
}
if(pos < 0||pos > pHead->length){
printf("insert postion error!\n");
return -2;
}
NodeList *curNode = pHead->next;
if(isEmpty(pHead)){
return -3;
}else{
if(pos==0){
while(curNode->next!=pHead->next){
curNode = curNode->next;
}
curNode->next = curNode ->next->next;
pHead->next = curNode ->next;
}else{
int i;
for(i=1;i<pos;i++){
curNode = curNode->next;
}
curNode ->next = curNode->next->next;
}
}
pHead->length--;
return 1;
}
4.3 其他代码
//创建头结点,length存储链表的长度 next指向下一个结点
typedef struct Header{
int length;
struct Header * next;
}Head;
//创建一个结点,data存放数据,next指向下一个结点
typedef struct Node{
int data;
struct Node *next;
}NodeList;
//创建一个链表,返回头结点
Head * createList(){
Head *phead = (Head*)malloc(sizeof(Head));
phead->length = 0;
phead->next = NULL;
return phead;
}
//判断是否为空
int isEmpty(Head *pHead){
if(pHead==NULL){
printf("not init head node!\n");
return -1;
}
if(pHead->length==0){
return 1;
}else{
return 0;
}
}
//打印
void print(Head *pHead){
if(pHead==NULL){
printf("not init headnode!\n");
return 0;
}
NodeList * node = pHead->next;
do{
printf("%d->",node->data);
node = node->next;
}while(node!=pHead->next);
printf(" length=%d\n",pHead->length);
}
4.4 测试
//循环链表
#include<stdio.h>
#include<stdlib.h>
int main(){
int i;
printf("Create Circle Node List: \n");
Head * pHead = createList();
printf("length = %d\n",pHead->length);
printf("\n");
printf("Insert Node: \n");
for(i=0;i<11;i++){
insertCircleList(pHead,i,i);
}
print(pHead);
printf("\n");
printf("Delete Node: \n");
deleteCircleNode(pHead,0);
print(pHead);
return 0;
}
输出结果:
输出结果