本文内容,参考自《大话数据结构》(程杰著) ,一部分自己修改,如:把C语言换成了Java语言。写作目的,意在加强记忆。
本文写作工具,使用 Typora。
线性表的顺序存储结构,它是有缺点的,最大的缺点就是插入和删除时需要移动大量元素,这显然就需要耗费时间。
线性表的链式存储结构
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。这就意味着,这些数据元素可以存在内存未被占用的任意位置。
在顺序结构中,每个数据元素只需要存数据元素信息就可以了。现在链式结构中,除了要存数据元素信息外,还要存储它后继元素的存储地址。
因此,为了表示每个数据元素 ai 与其直接后继数据元素 ai+1 之间的逻辑关系,对数据元素 ai 来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息。我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称做指针或链。这两部分信息组成数据元素 ai 的存储映像,称为结点(Node)。
n 个结点 (ai 的存储映像) 链结成一个链表,即为线性表的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。
对于链表来说,我们把链表中第一个结点的存储位置叫做头指针。最后一个结点,没有直接后继,该结点指针为空。有时,我们为了更加方便地对链表进行操作,会在单链表的第一个结点前附设一个结点,称之为头结点。头结点的数据域不存储任何信息,也可以存储如链表长度等附加信息。头结点的指针域存储指向第一个结点的指针。
头指针与头结点的异同
头指针 | 头结点 |
---|---|
头指针是链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针 | 头结点是为了操作的统一和方便而设立的,放在第一元素的结点之前,其数据域一般无意义 |
头指针具有标识作用,所以常用头指针冠以链表的名字 | 有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其它结点的操作就统一了 |
无论链表是否为空,头指针均不为空,头指针是链表的必要元素 | 头结点不一定是链表必须元素 |
链式结构Java实现
public class Node {
//为了方便,这两个变量都使用public,而不用private就不需要编写get、set方法了。
//存放数据的变量,简单点,直接为int型
public int data;
//存放结点的变量,默认为null
public Node next;
//构造方法,在构造时就能够给data赋值
public Node(int data){
this.data = data;
}
}
复制代码
/**
* 增加操作
* 直接在链表的最后插入新增的结点即可
* 将原本最后一个结点的next指向新结点
*/
public void addNode(Node node){
//链表中有结点,遍历到最后一个结点
Node temp = head; //一个移动的指针(把头结点看做一个指向结点的指针)
while(temp.next != null){ //遍历单链表,直到遍历到最后一个则跳出循环。
temp = temp.next; //往后移一个结点,指向下一个结点。
}
temp.next = node; //temp为最后一个结点或者是头结点,将其next指向新结点
}
复制代码
/**
* insertNodeByIndex:在链表的指定位置插入结点。
* 插入操作需要知道1个结点即可,当前位置的前一个结点
* index:插入链表的位置,从1开始
* node:插入的结点
*/
public void insertNodeByIndex(int index,Node node){
//首先需要判断指定位置是否合法,
if(index<1||index>length()+1){
System.out.println("插入位置不合法。");
return;
}
int length = 1; //记录我们遍历到第几个结点了,也就是记录位置。
Node temp = head; //可移动的指针
while(head.next != null){//遍历单链表
if(index == length++){ //判断是否到达指定位置。
//注意,我们的temp代表的是当前位置的前一个结点。
//前一个结点 当前位置 后一个结点
//temp temp.next temp.next.next
//插入操作。
node.next = temp.next;
temp.next = node;
return;
}
temp = temp.next;
}
}
复制代码
/**
* 通过index删除指定位置的结点,跟指定位置增加结点是一样的,先找到准确位置。然后进行删除操作。
* 删除操作需要知道1个结点即可:和当前位置的前一个结点。
* @param index:链表中的位置,从1开始
*
*/
public void delNodeByIndex(int index){
//判断index是否合理
if(index<1 || index>length()){
System.out.println("给定的位置不合理");
return;
}
//步骤跟insertNodeByIndex是一样的,只是操作不一样。
int length=1;
Node temp = head;
while(temp.next != null){
if(index == length++){
//删除操作。
temp.next = temp.next.next;
return;
}
temp = temp.next;
}
}
复制代码
长度思路:
注意:头结点不作为链表中的结点,不算入链表长度
1.拿到第一个结点
2.让temp的指针向后移动,不断指向下一结点,length++
3.若temp为空,则是链表最后一个结点了,退出循环
/**
* 计算单链表的长度,也就是有多少个结点
* @return 结点个数
*/
public int length() {
int length=0;
Node temp = head;
while(temp.next != null){
length++;
temp = temp.next;
}
return length;
}
复制代码
遍历思路:
1.拿到第一个结点
2.让temp的指针向后移动,不断指向下一结点
3.若temp为空,则是链表最后一个结点了,退出循环
/**
* 遍历单链表,打印所有data
*/
public void print(){
Node temp = head.next;
while(temp != null){
System.out.print(temp.data+",");
temp = temp.next;
}
System.out.println();
}
复制代码
单链表结构与顺序存储结构优缺点
存储分配方式 | 时间性能 | 空间性能 |
---|---|---|
顺序存储结构用一段连续的存储单元依次存储线性表的数据元素 | 查找,顺序结构0(1),单链表0(n) | 顺序存储结构需要预分配存储空间,分大了,浪费;分小了,容易溢出 |
单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素 | 插入和删除,顺序结构0(n),单链表0(1) | 单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制 |