数据结构之链表的使用与实现

一、链表

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

  

   使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的存取往往要在不同的排列顺序中转换。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。

二、链表的使用(java)

  ArrayList 是一个数组队列,相当于动态数组。它由数组实现,随机访问效率高,随机插入、随机删除效率低。

  LinkedList 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率低。

1、ArrayList

  ArrayList就是传说中的动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了如下一些好处:动态的增加和减少元素、实现了ICollection和IList接口、灵活的设置数组的大小。ArrayList是以数组方式实现的链表,是非同步的,在多线程的环境下使用需要同步。

  常用方法见:百度百科

  

一个简单地使用例子:  

ArrayList<Integer> list=new ArrayList<>();
for( int i=0;i<10;i++ ) //给数组增加10个Int元素
    list.add(i);
list.remove(5);//将第6个元素移除
for( int i=0;i<3;i++ ) //再增加3个元素
    list.add(i+20);
String str=list.toString();
System.out.println(list);

2、LinkedList

  LinkedList的使用方法和ArrayList相似,知识实现方法不同,ArrayList是采用数组的方式,而LinkedList是采用链表的方式实现的如果集合元素在生成之后变化不大,使用ArrayList,如果数据经常发生变化,应该使用LinkedList。

三、链表的实现(java)

1、单链表

  单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。

public class Node {
     protected Node next; //指针域
     protected int data;//数据域  

     public Node( int data) {
           this. data = data;
     }
}
public class LinkList {
     public Node first; // 定义一个头结点
     private int pos = 0;// 节点的位置  

     public LinkList() {
           this. first = null;
     }  

     // 插入一个头节点
     public void addFirstNode( int data) {
          Node node = new Node(data);
          node. next = first;
          first = node;
     }  

     // 删除一个头结点,并返回头结点
     public Node deleteFirstNode() {
          Node tempNode = first;
          first = tempNode. next;
          return tempNode;
     }  

     // 在任意位置插入节点 在index的后面插入
     public void add(int index, int data) {
          Node node = new Node(data);
          Node current = first;
          Node previous = first;
          while ( pos != index) {
              previous = current;
              current = current. next;
              pos++;
          }
          node. next = current;
          previous. next = node;
          pos = 0;
     }  

     // 删除任意位置的节点
     public Node deleteByPos( int index) {
          Node current = first;
          Node previous = first;
          while ( pos != index) {
              pos++;
              previous = current;
              current = current. next;
          }
           if(current == first) {
               first = first. next;
          } else {
               pos = 0;
              previous. next = current. next;
          }
           return current;
     }  

     // 根据节点的data删除节点(仅仅删除第一个)
     public Node deleteByData( int data) {
          Node current = first;
          Node previous = first; //记住上一个节点
          while (current. data != data) {
               if (current. next == null) {
                    return null;
              }
              previous = current;
              current = current. next;
          }
           if(current == first) {
               first = first. next;
          } else {
              previous. next = current. next;
          }
           return current;
     }
}

2、双向链表

  双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。

public class DoublyLinkList {
    private class Node{
        private Object obj;
        private Node left = null;
        private Node right = null;  

        Node(Object obj){
            this.obj = obj;
        }
    }  

    private Node first = null;
    private Node last = null;  

    public void insertFirst(Object obj){
        Node node = new Node(obj);
        if(first == null){
            last = node;
        }else{
            node.right = first;
            first.left = node;
        }
        first = node;
    }  

    public void insertLast(Object obj){
        Node node = new Node(obj);
        if(first == null){
            first = node;
        }else{
            last.right = node;
            node.left = last;
        }
        last = node;
    }  

    public boolean insertAfter(Object target,Object obj){
        Node node = new Node(obj);
        Node cur = first;
        while(cur != null){
            if(cur.obj.equals(target)){
                node.right = cur.right;
                node.left = cur;
                if(cur == last)
                    last = node;
                else
                    cur.right.left = node;
                cur.right = node;
                return true;
            }
            cur = cur.right;
        }
        return false;
    }  

    public Object deleteFirst() throws Exception{
        if(first == null)
            throw new Exception("empty!");
        Node temp = first;
        if(first.right == null){
            first = null;
            last = null;
        }else{
            first.right.left = null;
            first = first.right;
        }
        return temp;
    }  

    public Object deleteLast() throws Exception{
        if(first == null)
            throw new Exception("empty!");
        Node temp = last;
        if(first.right == null){
            first = null;
            last = null;
        }else{
            last.left.right = null;
            last = last.left;
        }
        return temp;
    }  

    public Object delete(Object obj) throws Exception{
        if(first == null)
            throw new Exception("empty!");
        Node cur = first;
        while(cur != null){
            if(cur.obj.equals(obj)){
                if(cur == last)
                    last = cur.left;
                else
                    cur.right.left = cur.left;
                if(cur == first)
                    first = cur.right;
                else
                    cur.left.right = cur.right;
                return obj;
            }
            cur = cur.right;
        }
        return null;
    }
}  

3、单向循环链表

  循环链表是另一种形式的链式存贮结构。它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。 

单向循环链表和单链表实现的区别:

1.)添加一个结点到单向循环链表末尾时,必须使其最后一个结点的指针指向表头结点,而不是象单链表那样置为null。

2.)判断是否到达表尾时,单向循环链表可以判断该结点是否指向头结点,单链表只需要知道是否为null。

// 结点类,包含结点的数据和指向下一个节点的引用
public class Node<E> {
    private E data; // 数据域
    private Node<E> next; // 指针域保存着下一节点的引用

    public Node(E data) {
        this.data = data;
    }

    public Node(E data, Node<E> next) {
        this.data = data;
        this.next = next;
    }

    public E getData() {
        return data;
    }

    public void setData(E data) {
        this.data = data;
    }

    public Node<E> getNext() {
        return next;
    }

    public void setNext(Node<E> next) {
        this.next = next;
    }
}

public class CircularLinkedList<E> {
    private Node<E> tail; // 尾结点
    private int size; // 链表长度

    public CircularLinkedList() {
        tail = null;
        size = 0;
    }

    // 在头结点前插入
    public boolean addBeforeHead(E data){
        Node<E> newNode = new Node<E>(data);
        if(isEmpty()){
            tail = newNode;
            tail.setNext(newNode); // 尾结点指向头结点
            newNode.setNext(tail); // 头结点指向尾结点
        }else{
            Node<E> head = tail.getNext();
            tail.setNext(newNode);
            newNode.setNext(head);
        }
        size++;
        return true;
    }

    // 在尾结点后插入
    public boolean addAfterTail(E data){
        Node<E> newNode = new Node<E>(data);
        if(isEmpty()){
            tail = newNode;
            tail.setNext(newNode);
            newNode.setNext(tail);
        }else{
            Node<E> head = tail.getNext(); // 获取头结点
            tail.setNext(newNode); // 将原尾结点指向新结点
            tail = newNode; // 将新节点设置为尾结点
            newNode.setNext(head); // 将新尾结点指向头结点
        }
        size++;
        return true;
    }

    // 在某位置上插入(position为结点位置,不是角标)
    public boolean insert(int position,E data){
        if(position >= 1 && (position <= size + 1)){
            if(isEmpty() || position == 1){ // 在头结点前插入
                addBeforeHead(data);
            }else if(position == size + 1){ // 在尾结点后插入
                addAfterTail(data);
            }else{ // 在中间位置插入
                Node<E> preNode = get(position - 1); // 获取position的前一结点
                Node<E> originalNode = preNode.getNext(); // 获取未插入结点时position位置对应结点
                Node<E> newNode = new Node<E>(data);
                preNode.setNext(newNode);
                newNode.setNext(originalNode);
                size++;
                return true;
            }
        }
        return false;
    }

    // 删除对应位置的结点
    public E delete(int position){
        E result = null;
        if(position >= 1 && position <= size){
            if(position == 1){ // 删除头结点
                result = tail.getNext().getData();
                Node<E> afterHead = tail.getNext().getNext();
                tail.setNext(afterHead);
            }else if(position == size){ // 删除尾结点
                result = tail.getData();
                Node<E> preTail = get(position - 1);
                preTail.setNext(tail.getNext());
                tail = preTail;
                size--;
            }else{ // 删除其他结点
                Node<E> preNode = get(position - 1);
                Node<E> curNode = preNode.getNext();
                result = curNode.getData();
                preNode.setNext(curNode.getNext());
                size--;
            }
        }
        return result;
    }

    // 获取某个位置的结点
    public Node<E> get(int position){
        Node<E> targetNode = null;
        if(!isEmpty() && position >= 1 && position <= size){
            targetNode = tail.getNext(); // 获取头结点
            for(int i = 1; i < position ; i++){
                targetNode = targetNode.getNext(); // 循环获取对应位置的结点
            }
        }
        return targetNode;
    }

    // 获取链表的长度
    public int getSize(){
        return size;
    }

    // 判断链表是否为空
    public boolean isEmpty(){
        return size == 0;
    }

    // 打印链表中的数据
    public void display(){
        Node<E> node = tail.getNext();  // 获取头结点
        System.out.print("单向循环链表: ");
        for(int i = 0; i < size; i++){
            System.out.print(" " + node.getData());
            node = node.getNext();
        }
        System.out.println("");
    }
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-08 21:54:18

数据结构之链表的使用与实现的相关文章

基本数据结构:链表(list)

copy from:http://www.cppblog.com/cxiaojia/archive/2012/07/31/185760.html 基本数据结构:链表(list) 谈到链表之前,先说一下线性表.线性表是最基本.最简单.也是最常用的一种数据结构.线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的.线性表有两种存储方式,一种是顺序存储结构,另一种是链式存储结构. 顺序存储结构就是两个相邻的元素在内存中也是相邻的.这种存储方式的优点是

数据结构之链表单向操作总结

链表是数据结构的基础内容之一,下面就链表操作中的创建链表.打印链表.求取链表长度.判断链表是否为空.查找结点.插入结点.删除结点.逆转链表.连接链表.链表结点排序等进行总结. 1.创建表示结点的类,因为链表操作中需要比较结点,因此结点需要实现comparable接口. public class Node implements Comparable<Node> { private Object data; private Node next; //构造函数 public Node() { thi

数据结构-复杂链表的复杂

题目:请实现函数ComplexListNode*  Clone(ComplexListNode* pHead),复杂一个复杂链表.在复杂链表中,每个节点除了有一个Next指针指向下一个节点外,还有一个Sibling指向链表中的任意节点或者NULL. 分析:第一反应是先复制Next,再复制Sibling.但是这种方式需要两次遍历.时间性不是很好.所以利用一个长链表方式解决时间效率. /* 剑指offer面试题26 */ #include <iostream> #include <cstri

AT&amp;T汇编语言与GCC内嵌汇编,Linux内核数据结构之链表

最近在看<Linux内核源代码情景分析>,作者毛德操.书中刚开始介绍了AT&T汇编语言与GCC内嵌汇编,以及Linux内核数据结构之链表.可惜书中介绍的不够全面.因为推荐大家阅读下面两篇文章.很不错. AT&T汇编语言与GCC内嵌汇编:http://grid.hust.edu.cn/zyshao/Teaching_Material/OSEngineering/Chapter2.pdf. Linux内核数据结构之链表:http://www.cnblogs.com/Anker/p/

数据结构:链表的基本操作(创建,删除,插入,逆序,摧毁)

代码注释比较详细: #include <iostream> #include <cstdlib> using namespace std; struct Node{ int data; Node* next; }; Node* head = NULL; bool create() { head = (Node*)malloc(sizeof(Node)); if(NULL == head) return false; head->data = 0; head->next

数据结构--单向链表

C语言中,我们在使用数组时,会需要对数组进行插入和删除的操作,这时就需要移动大量的数组元素,但在C语言中,数组属于静态内存分配,数组在定义时就必须指定数组的长度或者初始化.这样程序一旦运行,数组的长度就不能再改变,若想改变,就只能修改源代码.实际使用中数组元素的个数也不能超过数组元素的最大长度,否则就会发生下标越界的错误(这是新手在初学C语言时肯定会遇到的问题,相信老师也会反复强调!!!但这种问题肯定会遇到,找半天找不到错误在哪,怪我咯???).另外如果数组元素的使用低于最大长度,又会造成系统资

python实现数据结构单链表

#python实现数据结构单链表 # -*- coding: utf-8 -*- class Node(object): """节点""" def __init__(self, elem): self.elem = elem self.next = None # 节点一开始初始化的时候并不知道下一个元素的地址,所以先设置为空 class SingLinkList(object): """单链表""

数据结构之链表(LinkedList)(三)

数据结构之链表(LinkedList)(二) 环形链表 顾名思义 环形列表是一个首尾相连的环形链表 示意图 循环链表的特点是无须增加存储量,仅对表的链接方式稍作改变,即可使得表处理更加方便灵活. 看一样著名的应用场景 我们就可以用环形单链表解决这个问题. 首先我们怎么构建一个环形链表 分析: 1. 先创建第一个节点, 让 first 指向该节点,并形成环形 2. 后面当我们每创建一个新的节点,就把该节点,加入到已有的环形链表中即可. 示意图: 代码: // 创建一个Boy类,表示一个节点 cla

数据结构:单向链表系列6--交换相邻两个节点1(交换数据域)

给定一个单向链表,编写函数交换相邻 两个元素 输入: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 输出: 2 -> 1 -> 4 -> 3 -> 6 -> 5 -> 7 输入: 1 -> 2 -> 3 -> 4 -> 5 -> 6 输出: 2 -> 1 -> 4 -> 3 -> 6 -> 5 通过观察发现:当输入的与元素个数是单数的时候,最后一位不参与交换

垃圾回收机制和数据结构栈链表

1.垃圾回收机制: (1)没有引用变量指向的对象,就是垃圾. 举例: Test t = new Test(); t=null; 那么之前创建的对象就是垃圾. (2)对象没有被使用是另外一种垃圾. new Test(); new Test().toString(); 区别在于第一个对象很明显没有指向,是垃圾.但是第二个不是,因为他被使用了. 2.回收时机. 通常情况下,要在满了的时候回收. 其次在调用 System.gc();//通常情况下会立刻回收.等效于Runtime.getRuntime.g