漫谈数据结构(二)——线性表2

250 阅读10分钟

风景图

作者个人博客 www.you3xuan.top/ 查看原文。
本文为线性表第二篇,如果读者想了解第一篇,请点击这里
源码地址: github.com/ThinkingXua…
如果对您有帮助,随手一个Star吧。

1、线性表的链式存储

  在链式存储中,结点之间的内存单元地址是不连续的。它的每一个结点包括数据域和下一个结点的地址。头结点的数据域只存放结点的长度,并指向第一个元素。尾结点指向NULL。如图所示:

链表
  因为内存单元不连续,所以哪里空闲的内存,都可以分配一个结点,提高了内存的利用率。又因为结点之间只通过地址连接,所以删除和插入结点效率高。又因为没有索引与结点对应,查找一个结点的时候,必须找到上一个结点,所以查询效率不高。

2、链式存储的实现

2.1 创建单链表

  分为三部分,创建头结点,创建普通结点,创建单链表。

  1. 创建头结点
//创建头结点,length存储链表的长度 next指向下一个结点 
typedef struct Header{
	int length;
	struct Header * next;
}Head;
  1. 创建普通结点
//创建一个结点,data存放数据,next指向下一个结点 
typedef struct Node{
	int data;
	struct Node *next; 
}ListNode;
  1. 创建链表
//创建一个链表,返回头结点 
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;
}

输出结果:

输出结果