《大话数据结构》--线性表链式存储

373 阅读4分钟

链式存储

顺序存储的不足

插入和删除时需要移动大量元素。

链式存储

为了表示每个数据元素ai 与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说**,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储数据元素信息的域称为数据域**,把存储直接后继位置的域称为指针域。指针域中存储的信息称做指针或链。结点由存放数据元素的数据域和存放后继结点地址的指针域组成

n个结点(a的存储映像)链结成-一个链表, 即为线性表(a1, a2, .. an)的链式存储结构,因为此链表的每个结点中只包含一个指针域, 所以叫做单链表。

头指针:链表中第一个结点的存储位置

头指针和头结点的异同

头指针

  • 头指针是指链表指向第一个结点的指针,若链表有头结点,则指向头结点的指针。
  • 头指针具有标识作用,所以常用头指针冠以链表的名字。
  • 无论链表是否为空,头指针均不为空。头指针是链表的必要元素

头结点

  • 头结点是为了操作的统一和方便而创立的,放在第一元素的结点之前,数据域一般无意义。
  • 有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其他结点的操作统一
  • 头结点不一定是链表的必须要素

单链表

存储结构

static class Node {    
    Integer data;    
    Node next;    
    public Node() {    }
}

单链表的读取

获取链表第i个数据的算法思路

  1. 声明一个指针p指向链表的第一个结点,初始化j从1开始。
  2. 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1.
  3. 若到链表末尾p为空,说明第i个结点不存在
  4. 否则查找成功,返回结点p的数据

代码

static Integer getElem(Node head, int i) throws Exception {    
    int j = 1;    
    Node p = head.next; // 声明p并指向链表的第一个结点    
    while(p != null && (j < i)) { // p不为空切计数器j还没有等于i时,循环继续        
        p = p.next;        
        ++j;    
    }    
    if(p == null || j>i) {        
        throw new Exception("第i个结点不存在");    
    }    
    return p.data;
}

单链表的插入

图示

思路

  1. 声明一个结点p指向链表头结点,初始化计数器j从1开始
  2. 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1
  3. 若看到链表末尾p为空,则说明第i个结点不存在。
  4. 否则查找完成,在系统中生成一个空结点s。
  5. 将数据元素e赋值给s.data
  6. 单链表的插入标准语句 s.next = p.next, p.next = s

代码

static void listInsert(Node head, int i, Integer elementData) throws Exception {    
    Node p = head;    
    int j = 1;    
    while(p != null && j<i) { // 寻找第i-1个结点        
        p = p.next;        
        ++j;    
    }   
    if(p == null || j > i) {        
        throw new Exception("第i个结点不存在");    
    }    
    Node s = new Node();    
    s.data = elementData;    
    s.next = p.next;    
    p.next = s;
}

单链表的删除

图示

思路

  1. 声明一个结点p指向链表头指针,初始化计数器j从1开始
  2. 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1
  3. 若看到链表末尾p为空,则说明第i个结点不存在。
  4. 否则查找完成,将预删除的结点p.next赋值给q。
  5. 单链表的删除标准语句 p.next = q.next
  6. 将q结点中的数据赋值给e,返回

代码

static Integer listDelete(Node head, int i) throws Exception {    
    Node p = head;    
    int j = 1;    
    while(p.next != null && j<i) { // 寻找第i-1个结点        
        p = p.next;        
        ++j;    
    }    
    if(p.next == null || j > i) {        
        throw new Exception("第i个结点不存在");    
    }    
    Node q = p.next;    
    p.next = q.next;    
    return q.data;
}

单链表的整表创建

思路

  1. 声明一指针p和计数器变量i
  2. 初始化以空链表L
  3. 让L的头结点的指针指向NULL,即简历一个带头结点的单链表
  4. 循环 (1)生成以新结点赋值给p。(2)随机生成一数字赋值给p的数据域p.data。(3)将p插入到头结点与前一新结点之间

代码

static void createListHead(Node head, int n) {    // 新建头结点    
    Node tail = head;    
    for (int i=0; i<n; i++) {        
        Node p = new Node();        
        p.data = new Random().nextInt(100);        
        // 将表尾结点的指针指向新创建的结点        
        tail.next = p;        
        // 将新创建的结点当做表尾结点       
        tail = p;    
    }    
    tail.next = null; // 当前链表结束
}

单链表的整表删除

思路:

  1. 声明一结点p和q。
  2. 将第一个结点赋值给p
  3. 循环(1)将下一结点赋值给p。(2)释放p。(3)将q赋值给p

代码

static void clearList (Node head) {    
    Node p = head.next;    
    Node q;    
    while(p != null) {        
        q = p.next;        
        p.next = null;        
        p = q;    
    }    
    head.next = null;
}

测试

代码

public static void main(String[] args) throws Exception {    Node head = new Node();    createListHead(head, 10);    System.out.println("创建");    printList(head);    System.out.println("获取元素(3)");    System.out.println(getElem(head,3));    System.out.println("插入前");    printList(head);    listInsert(head, 4, 13);    System.out.println("插入后(i=4, elementData = 13)");    printList(head);    System.out.println("删除元素(5):" + listDelete(head, 5));    System.out.println("删除后");    printList(head);    System.out.println("清空");    clearList(head);    System.out.println("清空后");    printList(head);}

结果

单链表结构和顺序存储结构优缺点

存储分配方式

  • 顺序存储结构用一段连续的存储单元一次存储线性表的数据元素
  • 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素

时间性能

  • 查找:顺序存储结构O(1)。单链表O(n)
  • 插入和删除(1) 顺序存储结构需要平均一定表长一半元素,时间O(n)。(2)单链表在找出某位置得到指针后,插入和删除时间为O(1)
  • 空间性能(1)顺序存储结构需要预分配存储空间,分大了,浪费,小了易发生上溢(2)单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制

双向链表

双向链表是在单链表的每个结点上,再设置一个指向其前驱结点的指针域

存储结构

static class Node {    
    Integer data;    
    Node next;    
    Node prior;    
    public Node() {    }
}

插入数据

删除数据