`

双向链表之我的理解

 
阅读更多
谈到数据结构性能, 时常被问到ListLink的区别, 在回答之后,不可能避免的会将问题的焦点引到这两种存储方式的底层实现上来. List较为简洁,线形存储即可,Link基本结构, 则是考察的重点.例如说,双向链表.Java JDK中的java.util.LinkedList就是一个双链表。

 

双向链表的结构如下,每个节点包括3个属性(前节点引用,后节点引用,数值)。这两个引用,指向它的前后的节点,这样就克服普通链表只能向后遍历的局限: (自己画的图真丑!!!)

 

来张网上的截图,H为头节点,valuenull,它的引用分别指向最前和最后的两个节点。这样从两头可以遍历整个链表。

[疑问]Java数据结构和算法(第二版)中,第一个节点的prev和最后一个节点的next指向的null,而网上的例子都指向head节点,望看到的回复下,谢谢了。


双向链表的插入:

双向链表的删除:

插入和删除是链表的优势所在,只需要改变4个引用的指向即可。而List则需移动改节点后面的所有元素,相对效率差了很多。

 

参考了网上很多代码实现,结合Java的面向对象的特点,整理了一份源码,如下:

 

文件一: MyList 接口, 仅一个实现类的情况下,可无视.

/**
 * 仅添加几个主要的功能方法.
 * size,isEmpty较为简单,未考虑
 * @author 完美天龙
 *
 */
public interface MyList {
	public void add(Object o);
	public void delete(int i);
	public void addLast(Object o);
	public void addFirst(Object o);
	public Object get(int i);
}
文件二:功能的实现类, 实现了MyList接口, 并拥有自己的具体实现方法.我加了很多注释,只要自己用心去看.肯定能理解.

 

public class MyDoubleLinkedList implements MyList {
	private Node head = new Node(null);
	private int size = 0;

	/**
	 * 新节点加添,默认至链表最尾段, 也就是head的前面.
	 * 
	 * @param o
	 */
	public void add(Object o) {
		addBefore(new Node(o), head);
	}

	/**
	 * 删除指定的节点
	 * 
	 * @param o
	 */
	public void delete(int i) {
		removeNode(getNodeByIndex(i));

	}

	/**
	 * 查询指定位置的节点的value
	 * 
	 * @param i
	 * @return
	 */
	public Object get(int i) {
		return getNodeByIndex(i).getValue();
	}

	/**
	 * 添加节点至链表的尾端,即添加至head之前.
	 * 
	 * @param o
	 */
	public void addLast(Object o) {
		addBefore(new Node(o), head);
	}

	/**
	 * 添加节点至链表的首位,即添加至head之后.
	 * 
	 * @param o
	 */
	public void addFirst(Object o) {
		addAfter(new Node(o), head);
	}

	/**
	 * 将新节点插入至已知的节点的前一个.
	 * 
	 * @param newNode
	 *            待添加的节点
	 * @param node
	 *            指定已知的节点
	 */
	private void addBefore(Node newNode, Node node) {
		// TODO Auto-generated method stub
		newNode.setNext(node);
		newNode.setPrev(node.getPrev());
		newNode.getNext().setPrev(newNode);
		newNode.getPrev().setNext(newNode);
		size++;
	}

	/**
	 * 将新节点插入至已知的节点的后一个.
	 * 
	 * @param newNode
	 *            待添加的节点
	 * @param node
	 *            指定已知的节点
	 */
	private void addAfter(Node newNode, Node node) {
		newNode.setPrev(node);
		newNode.setNext(node.getNext());
		newNode.getPrev().setNext(newNode);
		newNode.getNext().setPrev(newNode);
		size++;
	}

	/**
	 * 链表打印,默认以next方式向后遍历 当然,我们也可以向前prev遍历,国际惯例是next
	 */
	public String toString() {
		StringBuffer ss = new StringBuffer("[");
		Node node = head;
		for (int i = 0; i < size; i++) {
			node = node.getNext();
			if (i > 0) {
				ss.append(",");
			}
			ss.append(node.getValue().toString());
		}
		ss.append("]");
		return ss.toString();
	}

	/**
	 * 添加节点至指点索引处,需要如下两部操作: 1> 找到该索引处的node. 2> 将新节点插入到所返回的node之后.
	 * 
	 * @param i
	 *            指定索引
	 * @param o
	 *            新节点
	 * @return boolean 返回值表示是否插入成功
	 */

	public boolean add(int i, Object o) {
		addBefore(new Node(o), getNodeByIndex(i));
		return true;
	}

	/**
	 * 这个的i,应该理解为链表的第几个节点,从1开始算,而不是直接的直接的索引.所以第i个元素的索引应该为i-1.
	 * 从head节点开始,不断向后遍历,找到i-1对应的节点.
	 * 
	 * @param i
	 *            第i-1个节点.
	 * @return 返回i-1位置的Node.
	 */
	private Node getNodeByIndex(int index) {
		Node node = head;
		if (index < 0 || index > size) {// 位置非法则抛出索引越界异常
			throw new IndexOutOfBoundsException();
		} else {
			for (int j = 0; j < index; j++) {
				node = node.getNext();
			}
		}
		return node;
	}

	/**
	 * 删除指定值为value的节点. 1> 找到该节点在链表中的位置. 2> 更新该节点前后节点的指向,并将自己的前后指向设置为null.
	 * 
	 * @param node
	 */
	private void removeNode(Node node) {
		node.getPrev().setNext(node.getNext());
		node.getNext().setPrev(node.getPrev());
		node.setNext(null);
		node.setPrev(null);
		size--;
	}
	/**
	 * @return the head
	 */
	public Node getHead() {
		return head;
	}
	/**
	 * @param head
	 *            the head to set
	 */
	public void setHead(Node head) {
		this.head = head;
	}
	/**
	 * @return the size
	 */
	public int getSize() {
		return size;
	}
	/**
	 * @param size
	 *            the size to set
	 */
	public void setSize(int size) {
		this.size = size;
	}

}

 

文件三:测试类。模仿的网上的例子,受网络限制,我找不到原出处了,So Sorry!

public class MyDoubleLinkedListTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		MyDoubleLinkedList dll = new MyDoubleLinkedList();
		
		//1. 给链表添加节点.
		dll.add("乔峰");
		dll.add("段誉");
		dll.add("虚竹");
		dll.add("慕容复");		
		System.out.println(dll);
		
		//2. 添加节点至链表尾端.
		dll.addLast("天山童姥");
		System.out.println(dll);
		
		//3. 添加节点至链表头部.
		dll.addFirst("扫地僧");
		System.out.println(dll);
		
		//4. 添加节点至指定位置:如"王雨嫣"插入至队列第4个.
		dll.add(4,"王语嫣");
		System.out.println(dll);
		
		//5. 删除指定位置的节点,如 删除第4个节点,即刚才添加的"王语嫣".
		dll.delete(4);
		System.out.println(dll);
		
		//6. 查询指定位置的节点数值.
		System.out.println(dll.get(4));
		
	}

}

 测试类执行结果:

[乔峰,段誉,虚竹,慕容复]

[乔峰,段誉,虚竹,慕容复,天山童姥]

[扫地僧,乔峰,段誉,虚竹,慕容复,天山童姥]

[扫地僧,乔峰,段誉,王语嫣,虚竹,慕容复,天山童姥]

[扫地僧,乔峰,段誉,虚竹,慕容复,天山童姥]

虚竹

 

基本功能模拟成功.

 

  • 大小: 3.1 KB
  • 大小: 23.4 KB
  • 大小: 90.2 KB
  • 大小: 12.9 KB
  • 大小: 11.7 KB
分享到:
评论

相关推荐

    20多张无水印图熟练理解和掌握单链表-双向链表-循环链表(原理+C源码)

    双向链表的简介以及概念 11 双向链表的结点设计 11 双向链表的插入操作 13 双向链表的删除操作 14 双向链表的遍历 15 循环链表 16 循环链表概念 16 循环链表结点设计(以单循环链表为例) 16 循环单链表初始化 17 ...

    双向链表结构

    基于C语言的双向链表,对于C语言初学者具有很好的帮助,可以加深对双向链表的理解

    c++ 双向链表的实例

    mfc双向链表 注释详细 对于数据结构的理解有一定帮助

    C语言数据结构 双向链表的建立与基本操作

    下面总结双向链表与单链表之间的不同之处及我在实现过程中所遇到的问题。 1.双向链表的建立 双向链表在初始化时,要给首尾两个节点分配内存空间。成功分配后,要将首节点的prior指针和尾节点的next指针指向NULL,...

    单双向链表解读

    图解单双向链表的生成,容易理解。附有代码详解,可以测试其真伪。

    C语言实现双向链表

    本文给大家分享的是一段使用C语言实现双向链表的代码,完全是根据自己的理解和认识来编写的,希望大家能够喜欢,文章的最后附上了一个网友写的对于双向链表删除节点、插入节点、双向输出等操作的代码,也非常不错,...

    C++双向链表

    这是燕山大学计算机专业大二,老师给我们布置的上机实验,链表元素中要有指针指向动态分配的内存空间,练习析构函数的操作规则 ... 练习要点:理解并、交、差操作并不影响参与操作的集合,实现并交差操作.

    面试题36. 二叉搜索树与双向链表

    输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。 为了让您更好地理解问题,以下面的二叉搜索树为例: 我们希望将这个二叉搜索树转化为...

    【超详细图解】菜鸡如何理解双向链表的python代码实现

    近期碰到了双向链表代码实现这块硬骨头,花了半天的时间研究,我终于理解了!现在我将从我这只菜鸡的视角出发,边讲解边画图,来帮助更多和我一样被大量代码虐惨的同学。 双向链表的原理 原创文章 3获赞 1访问量 ...

    双向循环链表.zip

    对c语言中双向循环链表的简单应用。能实现链表中的(前插人)后插入,查找,删除,移动的相关功能,并配有较为详细的注释,还画有数据和指针的流向图,帮助你快速理解对于链表中 各指针的指向关系。(测试时需将函数...

    C语言链表相关面试题.zip

    链表 C语言链表相关的面试题在软件开发领域的面试中是非常常见的,这是因为链表作为一种基本...双向链表中的节点包含两个指针,一个指向前一个节点,另一个指向下一个节点;循环链表则是首尾相接的链表,形成一个闭环。

    可变分区存储-循环首次适应法

    使用双向链表的数据结构,用C语言成功实现了可变分区存储管理中的循环首次适应法,实现了对内存区的分配和释放管理。...有助于理解可变分区存储管理与用指针和结构体实现双向链表和在链表上的基本操作。

    双向链表例子和A交B

    很容易的程序,大一的同学可以看看 .都是小的程序.但是很有对自己的编程理解帮助

    链表类模板封装链表操作

    用模板实现链表的建立删除等操作,可以根据具体情况稍作修改即可实现链表操作,或直接使用,我在链表的学习和类模板的学习和以后的编程时这种链表构造思想觉得挺好的,有点面向对象的意思……粗浅的理解呵呵!

    双向循环链表

    把普通程序转换为链表,更好的帮助你理解链表的使用方法

    链表(复习)资料

    一、复习要点 本章复习的要点: 1、基本知识点 单链表是一种线性结构,链表各结点的物理存储可以是不连续的,因此各结点的逻辑次序与物理存放次序可以不一致。...最后是双向链表的定义,它的插入与删除操作的实现。

    数据结构链表

    单向链表,双向链表代码,用c++写的。代码详细,容易理解。

    JavaScript数据结构与算法之链表

    接下来就是介绍两种常见的链表: 单向链表,双向链表在JavaScript中的实现。 单向链表 链表中最简单的形式就是单向链表,链表中的节点都包含两个部分,第一部分储存着自身信息,第二部分则储存有指向下一节点的指针...

    程序员面试题精选100题

    程序员面试题精选100题 本资源是程序员面试题精选100题,涵盖了算法、数据结构、操作系统、计算机网络、数据库等多个领域...本题目考察了程序员对二元查找树、双向链表、递归算法、指针操作等知识点的理解和应用能力。

Global site tag (gtag.js) - Google Analytics