双链表操作

处理完了单链表,这次处理双链表。

在一个双链表中,每个节点都包含两个指针,指向前一个节点的指针和指向后一个节点的指针。这可以使我们以任何方式遍历双链表,甚至可以忽略前后地在双链表中访问。下面的图示展示了一个双链表:

下面是节点类型的声明文件:

double_linked_list_node.h

#ifndef _DOUBLE_LINKED_LIST
#define _DOUBLE_LINKED_LIST
typedef struct NODE{
	struct NODE * fwd;
	struct NODE* bwd;
	int value;
}Node;

#endif

现在,存在两个根指针:一个指向链表的第一个节点,另一个指向最后一个节点。这两个指针允许我们从链表的任何一端开始遍历链表。

我们可能想把两个指针分开来声明为两个变量。但这样一来,我们必须把两个指针都传递给插入函数。为根指针声明一个完整的节点更方便,只是它的值字段绝不会被使用。在我们的例子中,这个技巧只是浪费了一个整型值的内存空间。对于值字段非常大的链表,分开声明两个指针可能更好一些。另外,我们也可以在根节点的值字段中保存其他一些关于链表的信息。例如链表当前包含的节点的数量。

根节点的fwd字段指向链表的第一个节点,根节点的fwd字段指向链表的最后一个节点。如果链表为空,这两个字段都为NULL。链表第一个节点的bwd和最后一个节点的fwd字段都为NULL。在一个有序的链表中,各个节点将根据value字段的值升序排列。

当把一个节点后插入到一个链表中时,可能出现四种情况:

1.新值可能必须插入到链表的中间位置;

2.新值可能必须插入到链表的起始位置;

3.新值可能必须插入到链表的结束位置;

4.新值可能必须插入到链表的起始位置,又插入到链表的结束位置(即链表为空)

下面是实现代码:

dll_ins1.c

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

int dll_insert(Node *rootp,int value){
	Node *this;
	Node *next;
	Node *newnode;

	/*查看value是否已经存在于链表中,如果是就返回。否则
	 * 为新值创建一个新节点(newnode将指向它)。this将
	 * 指向应该在新节点之前的那个节点,next将指向在新节点
	 * 之后的那个节点*/
	for(this=rootp;(next=this->fwd)!=NULL;this=next){
		if(next->value==value)
			return 0;
		if(next->value>value)
			break;
	 }

	 newnode=(Node *)malloc(sizeof(Node));
	 if(newnode==NULL)
	 	return -1;
	newnode->value=value;

	/*把新值添加到链表中*/
	if(next!=NULL){
		if(this!=rootp)/*新值插入到了链表中间*/
		{
			newnode->fwd=next;
			this->fwd=newnode;
			newnode->bwd=this;
			next->bwd=newnode;

		}else{/*新值插入到链表的起始位置*/
			newnode->fwd=next;
			rootp->fwd=newnode;
			newnode->bwd=NULL;
			next->bwd=newnode;
		}
	}
	else{
		if(this!=rootp)/*新值插入到链表的结束位置*/
		{
			newnode->fwd=NULL;
			this->fwd=newnode;
			newnode->bwd=this;
			rootp->bwd=newnode;
		}else{/*原先链表为空*/
			newnode->fwd=NULL;
			rootp->fwd=newnode;
			newnode->bwd=NULL;
			rootp->bwd=newnode;
		}
	}
	return 1;
}

int main(){
	Node root;
	Node first,second,third;
	first.value=10;
	second.value=15;
	first.fwd=&second;
	second.fwd=NULL;
	second.bwd=&first;
	first.bwd=NULL;
	root.fwd=&first;
	root.bwd=&second;
	if(dll_insert(&root,12)){
		printf("insert success!\n");
	}

	return 0;
}

一开始,函数是this指向根节点。next指针始终指向this之后的哪一个节点。它的思路是这两个指针同步前进,直到新节点应该插入到这两者之间。for循环检查next所指节点的值,判断是否到达需要插入的位置。

如果在链表中找到新值,函数就简单返回。否则,当到达链表尾部或找到适当的插入位置时循环终止。在任何一种情况下,新节点都应该插入到this所指的节点后面。注意,在我们决定新值是否应该实际插入到链表之前,并不为它分配内存。如果事先分配内存,如果发现新值原先已经存在于链表中,就有可能发生内存泄露。

上面4种情况是分开实现的。当插入12时。下面这张图显示了for循环终止之后几个变量的状态:

然后,函数为节点分配内存,下面几条语句执行之后

newnode->fwd=next;

this->fwd=newnode;

newnode->bwd=this;

next->bwd=newnode;

就可以将新创建的节点插入链表中。

最后分析一下双链表和单链表:

传递给单链表的函数的参数是指向节点的指针的指针类型(Node **),但传递给双链表的是指向节点指针的类型(Node *),但其实传递给双链表的指向节点指针类型里面还包含着一个指针,所以它们本质上都是使用的指向节点指针的指针类型。观察图可以很清晰地看出这一点。

双链表省去了单链表中第三种方法的讨论,这是由于双链表中的边界条件我们直接分开处理了,就好像只讨论到了单链表中的第二种情况为止,而单链表中我们想法设法想将插入到起始位置这种情况和插入到中间这种情况统一到一起,于是出现了单链表中第3种处理思路。

时间: 2024-10-05 04:27:30

双链表操作的相关文章

双链表操作(转)

双链表的初始化,建立,插入,查找,删除. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 8

多目标遗传算法 ------ NSGA-II (部分源码解析)辅助变量 双链表操作 list.c

1 /* A custom doubly linked list implemenation */ 2 3 # include <stdio.h> 4 # include <stdlib.h> 5 # include <math.h> 6 7 # include "global.h" 8 # include "rand.h" 9 10 /* Insert an element X into the list at location

双链表的基本运算

#include<iostream> using namespace std; #include<malloc.h> typedef char ElemType; typedef struct DNode { ElemType data; struct DNode *prior; struct DNode *next; }DLinkNode; void CreateListF(DLinkNode *&L,ElemType a[],int n)//头插法 { DLinkNod

双链表(非循环)相关操作:创建、析构、删除、冒泡排序

struct dulnode { int val; dulnode *pre; dulnode *next; }; //这里创建的不是双循环链表 dulnode* create_dulnode(int n) { if (n <= 0) return NULL; int i = 0; dulnode *head, *p1, *p2; //生成头节点 head = new dulnode; head->val = rand() % RAND_MAX; head->pre = NULL; //

JAVA 链表操作:单连表和双链表

主要讲述几点: 一.链表的简介 二.链表实现原理和必要性 三.单链表示例 四.双链表示例 一.链表的简介 链表是一种比较常用的数据结构,链表虽然保存比较复杂,但是在查询时候比较便捷,在多种计算机语言都相应的应用,链表有多种类别,文章针对单链表和双链表进行分析.链表中数据就像被一个链条串联一起,轻易的可以实现数据的访问. 二.链表实现原理和必要性 这里只分析单链表和双链表.链表的实现过程是有些许复杂的,但是会带来许多好处.比如现在网购时代到来,商家发快递一般会将商品包装在盒子里并写上地址信息,快递

双链表内部迁移操作

说明:本文仅供学习交流,转载请标明出处,欢迎转载! 所谓双链表的迁移操作指的是将双链表中一部分连续的结点迁移到另外一个结点之前,为了说明这个定义,我们从下图来解释. 我们将[first,last)对应的结点迁移到position所指向的位置之前?那该怎么做呢?这不是一个算法设计题,因为不需要什么特别有效率的技巧,我只是想理清这个思路而已,因为双链表的增删操作如果不理清思路会很容易产生混乱. 下面我们给出该双链表结点的定义: template<class T> struct node { <

【数据结构】用C++实现双链表的各种操作(包括头删,尾删,插入,逆序,摧毁,清空等等)

//[数据结构]用C++实现双链表的各种操作(包括头删,尾删,插入,逆序,摧毁,清空等等) //头文件 #ifndef _LIST_H #define _LIST_H #include<iostream> using namespace std; template<class Type> class DList; template<class Type> class ListNode { friend class DList<Type>; public: L

数据结构--双链表的创建和操作

双向链表的定义 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱.所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点.一般我们都构造双向循环链表. 注意:在实现的过程中,要分清前驱指针和后继指针,不要把他们当成一个指针. 1 //双向链表的实现 2 template<typename T>struct node{ 3 T data; 4 node<T> *prior,*next; 5 }; 双向链表的实现 1

优先双链表

题目: 设有一个双链表,每个结点中除有prior,data和 next这3个域外,还有一个访问频度域 freq,在链表被启用前其值均初始化为0.每当在在链表上进行一次查找操作Locate(L, x)时,令元素值为x的结点中的freq域的值增加1,并使此链表中的结点保持按访问频度域递减的顺序排列,以便使频繁访问的结点总是靠近表头 (1)首先创建一个双链表. (2) 设计一个符合上述要求的Locate(L, x)函数. (3) 具有输出显示访问频度功能. (4) 要求程序通过一个主菜单进行控制,在主