数据结构与算法——双链表的实现

上次学习了单链表,这次来学习双链表。二者之间的区别是,单链表中的每个结点只存有后继结点的地址,而双链表中则存了两个地址,一个是前驱结点的地址,一个是后继结点的地址。

结构体

struct ListNode{
	int element;                       //数据域
	struct ListNode *next;		       //后继结点的地址
	struct ListNode *prev;             //前驱结点的地址
};

这里的结构体中存有了两个链表结点ListNode类型的指针,一个指向前驱结点,一个指向后继结点。头结点的prev置空,尾结点的next置空。

初始化链表

void InitListNode(struct ListNode *head){
	head->next=NULL;
	head->prev=NULL;
}

初始化链表就是对现有的头结点进行准备操作。头结点的数据域我们一般不会用到,所以可以不用管数据域。将next和prev置空,后续需要插入元素时,再对next进行操作。

插入操作

int InsertListNode(struct ListNode *head,int element,int index){
	if (index<1) return 0;           //判断插入位置是否合法
	struct ListNode *p=head;      
	while(index>1){                 //循环操作将指针移至待插入位置的前驱结点
		p=p->next;
		if (p==NULL) return 0;
		index--;
	}
	struct ListNode *node =malloc(sizeof(struct ListNode));  //分配新结点的内存空间
	if (node==NULL) return 0;         //判断空间是否分配成功
	node->element=element;
	if (p->next==NULL){              //第一种情况,插入在链表尾端
		p->next=node;                
		node->prev=p;
		node->next=NULL;
	}else{                           //第二种情况,插入不在尾端
		node->prev=p;
		node->next=p->next;
		p->next=node;
		node->next->prev=node;
	}
	return 1;
}

插入操作中,与单链表不同的是,我按插入位置是否在链表尾端分成了两种情况来讨论,这样便于理解。
第一种情况,插入位置在尾端,当指针通过循环到达指定位置的前驱结点时,因为下一个结点为空,也就意味着当前结点时尾结点,在此节点后再插入结点就是新的尾结点,而当前结点p就是待插入结点的前驱结点。这种情况逻辑上较为简单,只用将当前结点的next连向待插入结点,然后将新的尾结点的prev指向前驱结点,next置空。
第二种情况,插入位置不在尾端,循环结束后,如果下一结点非空,就代表当前结点不是尾结点,这时的p是待插入结点的前驱结点。这种情况稍微复杂一点,需要反复理解。第一步,先将待插入结点的next和prev分别指向p和p的后继结点。第二步,将p的next指向新的node结点,再将node的后继结点的prev指向node,这里注意node的后继结点的寻找方法,需要先用刚刚第一步中的node->next来找到后继结点,因为当p->next改变,不再指向这个结点时,现在只有node->next指向该结点,否则通过其它结点无法找到该结点,这里注意理解。
这里还需要注意的是,第一步和第二步的顺序不能颠倒。如果先操作第二步,将会出现的问题是,node->prevnode->next找不到前后两段需要连接的链表。

删除操作

int DeleteListNode(struct ListNode *head,int index){
	struct ListNode *p=head;
	if (index<1) return 0;                
	while(index>1){           //循环操作找到前驱结点
		p=p->next;
		if(p==NULL) return 0;
		index--;
	}
	if(p->next->next){            //需要删除的结点不是尾结点
		struct ListNode *q=p->next;
		p->next=p->next->next;
		p->next->prev=p;	
		free(q);
	}else{                      //需要删除的结点是尾结点
		struct ListNode *q=p->next;
		p->next=NULL;
		q->prev=NULL;
		free(q);
	}
	return 0;
}

删除操作为了便于理解,也按照是否为尾结点分成了两种情况。第一种情况,因为p是前驱结点,所以p->next是需要删除的结点,而判断p->next->next则是看需要删除的结点是否有后继结点,如果p->next->next存在,说明需要删除的结点后面仍存在结点,所以需要删除的结点不是尾结点,否则是尾结点。
当不是尾结点时,用一个新的指针指向需要删除的结点,否则当链表的指针跳过该结点连接后,会找不到该节点。当该结点是尾结点时,用一个新的指针标记后,直接将前驱结点的next置空即可。两种情况的最后,都需要使用free()函数释放被删除结点的内存,完成删除。

获取操作

int GetListNode(struct ListNode *head,int index){
	if(index<1) return 0;
	struct ListNode *p=head;
	while (index>1){
		p=p->next;
		index--;
	}
	return p->next->element;
}

这里单链表一样,通过循环操作,将指针移到前一个结点,返回该结点的后继结点的数据域。

查找操作

int FindListNode(struct ListNode *head,int target){
	struct ListNode *p=head;
	int i=0;
	while(p->next){
		p=p->next;
		i++;
		if(p->element==target) return i; 
	}
	return 0;
}

通过循环,遍历链表上的每一个结点,比较数据域和待查找元素,如果相等,返回该结点的位序,如果循环结束,还没有找到,那就返回错误信息0,代表未找到该元素。

获取链表长度

int ListNodeSize(struct ListNode *head){
	struct ListNode *p=head;
	int i=0;
	while(p->next){
		p=p->next;
		i++;
	}
	return i;
}

通过循环,遍历链表元素,每遍历一个,计数器自增一次,最后计数器计的数值就是链表长度,直接返回计数器就可以。

打印链表

void PrintListNode(struct ListNode *head){
	struct ListNode *p=head;
	while(p->next){
		p=p->next;
		printf("%d ",p->element);
	}
	printf("\n");
}

同样,为了方便查看链表情况,我们编写此函数来从头打印链表的每一个元素。

完整代码

#include <stdio.h>
#include <stdlib.h>

struct ListNode{
	int element;
	struct ListNode *next;
	struct ListNode *prev;
};

void InitListNode(struct ListNode *head){
	head->next=NULL;
	head->prev=NULL;
}

int InsertListNode(struct ListNode *head,int element,int index){
	if (index<1) return 0;
	struct ListNode *p=head;
	while(index>1){
		p=p->next;
		if (p==NULL) return 0;
		index--;
	}
	struct ListNode *node =malloc(sizeof(struct ListNode));
	if (node==NULL) return 0;
	node->element=element;
	if (p->next==NULL){
		p->next=node;
		node->prev=p;
		node->next=NULL;
	}else{
		p->next->prev=node;
		node->next=p->next;
		p->next=node;
		node->prev=p;
	}
	return 1;
}

int DeleteListNode(struct ListNode *head,int index){
	struct ListNode *p=head;
	if (index<1) return 0;
	while(index>1){
		p=p->next;
		if(p==NULL) return 0;
		index--;
	}
	
	if(p->next->next){
		struct ListNode *q=p->next;
		p->next=p->next->next;
		p->next->prev=p;	
		free(q);
	}else{
		struct ListNode *q=p->next;
		p->next=NULL;
		q->prev=NULL;
		free(q);
	}
	return 0;
}

int GetListNode(struct ListNode *head,int index){
	if(index<1) return 0;
	struct ListNode *p=head;
	while (index>1){
		p=p->next;
		index--;
	}
	return p->next->element;
}

int FindListNode(struct ListNode *head,int target){
	struct ListNode *p=head;
	int i=0;
	while(p->next){
		p=p->next;
		i++;
		if(p->element==target) return i; 
	}
	return 0;
}

int ListNodeSize(struct ListNode *head){
	struct ListNode *p=head;
	int i=0;
	while(p->next){
		p=p->next;
		i++;
	}
	return i;
}


void PrintListNode(struct ListNode *head){
	struct ListNode *p=head;
	while(p->next){
		p=p->next;
		printf("%d ",p->element);
	}
	printf("\n");
}


int main(){
	...
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值