笔者通过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;
}
运行截图:
谢谢阅读!