C语言实现链表(包含单链表和双向链表各项操作)

笔者通过C语言简单实现了单链表和双向链表的各类操作(增,删,查(包含在删操作的实现内))

文章最后有源码~~~~~

改 的操作基于增的操作,较为简单,就不另外实现了。。。

基本功能如下:
1.基本实现了链表的插入:
A.尾插法,B.头插法,C.指定位置插入
2.指定值的节点的删除
3.指定值的查找(包含在删除操作的实现代码内,所以就没有单独通过函数实现)
4.链表的打印

链表的概念:
链表是一种非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域(双向链表则会有 next 域和 pre 域 分别指向下一个和上一个节点)。

如下图所示:
在这里插入图片描述笔者通过代码实现如下的结构(包含单链表和双链表)
在这里插入图片描述
说明:
其中 size 域记录了链表内存放元素的个数
head 域指向链表的头节点
tail 域指向链表的尾节点
type 表示链表的类型(1 为单链表 ,2 为双向链表)

代码结构体说明:
Node节点定义:

typedef struct node
{
	Element data;
	struct node *next;		//指向下一节点 
	struct node *pre;		//指向前一节点 
}Node;

利用typedef 命名节点 Node
——以后声明时就不用再struct node p ; 了, 直接Node p ;即可
每个节点 包含 Element 数据域(这里定位int),next指针,pre指针。

链表变量结构体定义:

typedef struct list
{
	Node *head;	// 存放链表头的地址 
	Node *tail; // 存放链表尾的地址 
	int size;	// 链表内节点的个数
	int type;
	/*
	type = 1 表示单向链表
	type = 2 表示双向链表 
	*/
}List;

每个List 类型的变量 即代表一个链表
该变量包含size,type,tail,head域(详细功能代码注释有说明)

部分功能函数说明(具体原理限于篇幅,不再详述):
链表头插法实现:

void Insert_head(List * l ,Element key)		//头插法 即每次插入的元素放到第一位 
{
	Node * p = (Node *)malloc(sizeof(Node));
	p->data = key;
	p->next = p->pre = NULL;
	if(l->size == 0)
	{
		l->head = p;
		l->tail = p;
	}
	else
	{
		p->next = l->head;
		l->head->pre = p;	//type = 2 时需要的操作 
		l->head = p;	
	}	

	l->size += 1;
}

接受Element类型的参数,并为其开辟节点,将其插入到链表的头部位置。
在插入时不会因为链表类型而导致实现有所不同
——即在实现头插法和尾插法时单向链表和双向链表差异不大
尾插法实现:

void Insert_tail(List *l ,Element key) // 尾插法 即每次插入的元素放到最后一位 
{
	Node * p = (Node *)malloc(sizeof(Node));
	p->data = key;
	p->next = p->pre = NULL;
	
	if(l->size == 0)
	{
		l->head = l->tail = p;
	}
	else
	{
		l->tail->next = p;
		p->pre = l->tail;	//type = 2 时需要的操作 
		l->tail = p;	
	}
	l->size += 1;
} 

与头插法的不同在于每次插入在链表的最后(更接近于真实的情况),但实现起来比头插法复杂。
注意:尾结点的地址每次插入都有变动。

在指定位置插入节点功能:

void Insert_Loc(List *l , Element key , int position)
{
		
	Node * p = (Node *)malloc(sizeof(Node));
	p->data = key;
	p->next = p->pre = NULL;

    if(position>l->size)	//如果插入位置大于范围,就自动插到头 
	{
		Insert_tail(l,key);
		return ;
	}
    
	if(position == 1)
	{
		p->next = l->head;
		l->head->pre = p;	// type = 2 时才需要的操作 如果仅实现单向链表的话可以删除该句 
		l->head = p;
	}
	else
	{
		Node * q = l->head;
		int i = 1;	
		
		for(i=1 ; i<position-1 ; i++ , q=q->next);	
		/*
		 	跳到指定位置的前一个节点 
			如果是单向链表的话就无法找到前一节点的地址
			而无法完成指定位置的插入操作 (双向链表无所谓) 
		*/
		p->next = q->next;
		q->next->pre = p;	// type = 2 时才需要的操作 如果仅实现单向链表的话可以删除该句 
		q->next = p;
		p->pre =q; 		// type = 2 时才需要的操作 如果仅实现单向链表的话可以删除该句 
	}
	l->size += 1;
}

实现更为复杂
因为将节点插入到第一的位置需要改变head的地址,插入到最后位置则要改变tail的地址,而且在中间的插入与前两者情况不同。
——更详细的说明请看注释。

指定值所在节点的删除:

void Del_Node(List *l ,Element key)
{
	Node *p =NULL,*q =NULL;
	p = q = l->head;
	if(l->head->data == key)	//头节点 需要特殊处理 因为要改变 L->head 的值  
	{
		l->head = p->next;
		p->next->pre = NULL;
		free(p);	
	}
	else
	{
		while(p->next)
		{
			q=p->next;
			if(q->data == key )
			{
				if(q==l->tail)			//如果删除的是尾结点(特判) 
				{
					l->tail = p;
					p->next = NULL;
					free(q);
					return ;
				}
				if(l->type == 1)		//如果是单链表 
				{
					p->next = q->next;
					free(q);	
				}
				else if(l->type == 2)	//如果是双向链表 
				{
					q->pre->next = q->next;
					q->next->pre = q->pre;
					free(q); 	
				}
			}
			p=p->next;
		}
	}
	l->size -= 1;
}

因为特殊的结构,在删除头节点和尾结点都涉及 head 与 tail 值的更新。
同时单链表和双向链表各自的操作又有所不同。
所以实现代码涉及到许多的特判较为繁琐(笔者能力有限,如有更好的方法,欢迎分享)
链表的打印:

void Print_List(List *l)
{
	Node *p = l->head;
	for(p ; p ; p=p->next)
	{
		printf("%d ",p->data);
	}

	if(l->type == 2)
	{
		printf("\n");
		p = l->tail;
		for( ; p ; p=p->pre)
		{
			printf("%d ",p->data);
		}
		printf("\n");
	}
}

会根据链表的类型而做相应的打印

释放链表:

void Destory_List(List *l)
{
	Node *p,*q;
	for(p=l->head ; p ; q=p,p=p->next)
	{
		free(q);
	}

	free(l);
}

释放动态开辟的内存

最终源代码如下:

#include<stdio.h>
#include<stdlib.h>
typedef int Element;

typedef struct node
{
	Element data;
	struct node *next;		//指向下一节点 
	struct node *pre;		//指向前一节点 
}Node;

typedef struct list
{
	Node *head;	// 存放链表头的地址 
	Node *tail; // 存放链表尾的地址 
	int size;
	int type;
	/*
	type = 1 表示单向链表
	type = 2 表示双向链表 
	*/
}List;

List * Init_List(int type)
{
	List * l = (List *)malloc(sizeof(List));
	l->head = NULL;
	l->tail = NULL;
	l->size = 0;
	l->type = type; 
	return l;
}

void Insert_head(List * l ,Element key)		//头插法 即每次插入的元素放到第一位 
{
	Node * p = (Node *)malloc(sizeof(Node));
	p->data = key;
	p->next = p->pre = NULL;
	if(l->size == 0)
	{
		l->head = p;
		l->tail = p;
	}
	else
	{
		p->next = l->head;
		l->head->pre = p;	//type = 2 时需要的操作 
		l->head = p;	
	}	

	l->size += 1;
}

void Insert_tail(List *l ,Element key) // 尾插法 即每次插入的元素放到最后一位 
{
	Node * p = (Node *)malloc(sizeof(Node));
	p->data = key;
	p->next = p->pre = NULL;
	
	if(l->size == 0)
	{
		l->head = l->tail = p;
	}
	else
	{
		l->tail->next = p;
		p->pre = l->tail;	//type = 2 时需要的操作 
		l->tail = p;	
	}
	l->size += 1;
} 

void Insert_Loc(List *l , Element key , int position)
{
		
	Node * p = (Node *)malloc(sizeof(Node));
	p->data = key;
	p->next = p->pre = NULL;

    if(position>l->size)	//如果插入位置大于范围,就自动插到尾 
	{
		Insert_tail(l,key);
		return ;
	}
 
	if(position == 1)
	{
		p->next = l->head;
		l->head->pre = p;	// type = 2 时才需要的操作 如果仅实现单向链表的话可以删除该句 
		l->head = p;
	}
	else
	{
		Node * q = l->head;
		int i = 1;	
		
		for(i=1 ; i<position-1 ; i++ , q=q->next);	
		/*
		 	跳到指定位置的前一个节点 
			如果是单向链表的话就无法找到前一节点的地址
			而无法完成指定位置的插入操作 (双向链表无所谓) 
		*/
		p->next = q->next;
		q->next->pre = p;	// type = 2 时才需要的操作 如果仅实现单向链表的话可以删除该句 
		q->next = p;
		p->pre =q; 		// type = 2 时才需要的操作 如果仅实现单向链表的话可以删除该句 
	}
	l->size += 1;
}

void Del_Node(List *l ,Element key)
{
	Node *p =NULL,*q =NULL;
	p = q = l->head;
	if(l->head->data == key)	//头节点 需要特殊处理 因为要改变 L->head 的值  
	{
		l->head = p->next;
		p->next->pre = NULL;
		free(p);	
	}
	else
	{
		while(p->next)
		{
			q=p->next;
			if(q->data == key )
			{
				if(q==l->tail)			//如果删除的是尾结点(特判) 
				{
					l->tail = p;
					p->next = NULL;
					free(q);
					return ;
				}
				if(l->type == 1)		//如果是单链表 
				{
					p->next = q->next;
					free(q);	
				}
				else if(l->type == 2)	//如果是双向链表 
				{
					q->pre->next = q->next;
					q->next->pre = q->pre;
					free(q); 	
				}
			}
			p=p->next;
		}
	}
	l->size -= 1;
}

//如果判断为双向链表 就输出两种打印(从头到尾,从尾到头)
void Print_List(List *l)	
{
	Node *p = l->head;
	for(p ; p ; p=p->next)
	{
		printf("%d ",p->data);
	}

	if(l->type == 2)
	{
		printf("\n");
		p = l->tail;
		for( ; p ; p=p->pre)
		{
			printf("%d ",p->data);
		}
		printf("\n");
	}
}

void Destory_List(List *l)
{
	Node *p,*q;
	for(p=l->head ; p ; q=p,p=p->next)
	{
		free(q);
	}
//	free(l->head);
	free(l);
}

int main()
{
	Element a[]={1,2,3,4,5};
	Element key = 10;
	List *l = Init_List(2);			//接收参数 1代表单向链表   2代表双向链表 
	int i =0;
	for(i=0 ; i<sizeof(a)/sizeof(Element) ; i++)
	{
		Insert_tail(l,a[i]);		//也可以用头插法实现 
	}
	
	Insert_Loc(l,30,3);				// 将 30 插入到三号位置  
	Insert_Loc(l,10,1);				// 将 10 插入一号位置 
	Insert_Loc(l,20,l->size+1);		// 将 20 插入到最后 
	Print_List(l);
	 
	Del_Node(l,key);					
	Print_List(l);
	
	printf("\nType:%d Size:%d\n", l->type,l->size);
	Destory_List(l);
	return 0;
} 

运行截图:
在这里插入图片描述
谢谢阅读!

发布了7 篇原创文章 · 获赞 12 · 访问量 773

猜你喜欢

转载自blog.csdn.net/M_H_T__/article/details/103312291
今日推荐