线性表——零个或多个数据元素的有限序列。
注意:
首先它是一个序列。元素之间是有顺序的,若存在多个元素,则第一个元素无前驱,最后一个元素无后继,其他每个元素都有且只有一个前驱和后继。线性表的元素个数n定义为线性表的长度,当 n = 0 时,称为空表。
public interface ListADT {
public void clear();// 置空
public boolean isEmpty();// 判空
public int length();// 顺序表的长度
public Object get(int index) throws Exception;// 获取指定位置的元素
public void add(Object data);// 添加数据(添加至表尾)
public void insert(int index, Object data) throws Exception;// 指定位置插入数据
public void delete(int index) throws Exception;// 删除指定位置的元素
public int indexOf(Object data);// 返回给定数据的索引
public void display();// 遍历
}
用一段地址连续的存储单元依次存储线性表的数据元素(即用数组保存数据),也称顺序表。所有元素的存储位置取决于第一个元素的存储位置。
(1)插入数据的三种情况:
(2)插入算法的思路:
(1)移除数据的两种情况:
(2)移除元素算法的思路:
数组长度是存放线性表的存储空间的长度,线性表的长度是线性表中数据元素的个数。任意时刻,线性表的长度应该小于数组长度。
优点:可以快速存取;
缺点:插入和删除操作需要移动大量元素;当线性表长度变化较大时,难以确定存储空间的容量;造成存储空间的“碎片”。
public class SequenceList implements ListADT{
private int DEFAULT_SIZE = 5;// 数组的默认长度
private Object[] dataArray;
private int capacity;// 数组的长度
private int size = 0;// 顺序表的长度(保存顺序表当前数据的个数)
// 无参构造器:创建一个默认长度的空顺序表
public SequenceList(){
capacity = DEFAULT_SIZE;
dataArray = new Object[capacity];
}
// 有参构造器:用指定数据创建一个默认长度的顺序表
public SequenceList(Object data){
this();
dataArray[0] = data;
size++;
}
// 有参构造器:创建指定大小的顺序表
public SequenceList(Object data, int initSize){
capacity = initSize;
dataArray = new Object[capacity];
dataArray[0] = data;
size++;
}
@Override
// 置空
public void clear() {
Arrays.fill(dataArray, null);
size = 0;
}
@Override
// 判空
public boolean isEmpty() {
return size == 0;
}
@Override
// 返回顺序表的长度
public int length() {
return size;
}
@Override
// 获取指定位置的数据
public Object get(int index) throws Exception {
if(index < 0 || index > size){
throw new IndexOutOfBoundsException("第" + index +"个元素不存在");
}
return dataArray[index];
}
// 添加数据(添加至表尾)
public void add(Object data) {
// 顺序表已满,扩容
if(capacity == size){
expand();
}
dataArray[size] = data;
size++;
}
@Override
// 在指定位置插入数据
public void insert(int index, Object data) throws Exception {
// 顺序表已满,扩容
if(capacity == size){
expand();
}
if(index < 0 || index > size){
throw new IndexOutOfBoundsException("插入位置不合理!");
}
// 插入位置及之后的元素后移一位
for (int i = size; i > index; i--) {
dataArray[i] = dataArray[i - 1];
}
dataArray[index] = data;
size++;// 表长加一
}
// 扩容
private void expand(){
capacity = capacity * 2;
dataArray = Arrays.copyOf(dataArray, capacity);
}
@Override
// 删除指定位置的数据
public void delete(int index) throws Exception {
if(index < 0 || index > size){
throw new Exception("删除位置不合理!");
}
// 被删除数据之后的数据左移
for (int i = index; i < size; i++) {
dataArray[i] = dataArray[i + 1];
}
size--;
}
@Override
// 返回给定数据的索引
public int indexOf(Object data) {
int i = 0;
// 查找给定数据的位置
while(i < size && !dataArray[i].equals(data)){
i++;
}
if(i < size){
return i;
}else{
return -1;
}
}
@Override
public void display() {
if(isEmpty()){
System.out.println("顺序表为空!");
}else{
for (int i = 0; i < size; i++) {
System.out.print(dataArray[i] + " ");
}
System.out.println();
}
}
}
用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。在链式结构中,除了要存储数据元素外,还要存储它的后继元素的存储地址。
为了表示数据元素 a i a_i ai 与其直接后继数据元素 a i + 1 a_{i+1} ai+1 之间的逻辑关系,对数据元素 a i a_i ai 来说,除了存储其本身的信息之外,还需要存储一个指示其直接后继的信息(即直接后继的存储位置信息)。把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称作指针或链。这两部分信息组成数据据元素 a i a_i ai 的存储映像,称为结点。
n 个结点链结成一个链表,即为线性表的链式存储结构,因为此链表的每个结点只包含一个指针域,又叫单链表。如图:
链表中第一个结点的存储位置叫做头指针。最后一个结点没有直接后继,规定最后一个结点指针为“空”。
为了方便操作,会在单链表的第一个结点之前附设一个节点,称为头结点。头结点的数据域可以不存储信息,也可以存储线性表的长度信息,头结点的指针域存储指向第一个结点的指针。
头指针和头结点的异同:
头指针:
头结点:
结点由存放数据元素的数据域和存放后继结点地址的指针域组成。如图:
读取第 i 个元素,必须从头开始找。算法思路:
但是,由于链表的结构中没有定义表长,所以事先不知道要循环多少次,因此不方便使用 for 循环实现。其核心思想是“工作指针后移”。
假设存储元素 e 的结点为 s,将结点 s 插入到结点 p 和 p->next之间,只需改变 s->next 和 p->next 的指针,即把 p 的后继结点改成 s 的后继结点,再把结点 s 变成 p 的后继结点。如下图:
代码如下:
s->next = p->next;
p->next = s;
注意: 两行代码的顺序不能交换。
算法思路:
假设 q 是 p 的直接后继结点,删除结点 q,就是把 p 的后继结点改成 p 的后继的后继结点。即 p.next = p.next.next;
算法思路:
链表所占用空间的大小和位置不需要预先分配,可以即时生成,其创建过程就是一个动态生成链表的过程,即从“空表”的初始状态起,依次建立各元素结点,并逐个插入链表。
算法思路(头插法):
还可以使用尾插法,需要注意:要始终让 r 指向尾结点,并且在循环结束后还应该让这个结点的指针域置空。
抽象数据类型:
public interface LinkListADT<T> {
public boolean isEmpty();
public int size();
public void clear();
public T get(int index);
public void add(T element); //添加元素,默认尾部添加
public void addFirst(T element);
public void addLast(T element);
public void add(int index, T element);
public void remove(T element);
public void display();
}
单链表代码实现:
public class LinkList<T> implements LinkListADT<T>{
// 内部类,节点类
private class Node{
public Object data;// 数据域
public Node next;// 指针域
public Node(){}
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
}
private Node head;// 头结点
private Node tail;// 尾结点
private int size;// 保存已有的结点数
// 创建空链表
public LinkList(){
head = null;
tail = null;
}
// 以指定元素创建链表,只含一个元素
public LinkList(T element){
head = new Node(element, null);
tail = head;// //只有一个结点,header和tail都指向该结点
size++;
}
@Override
// 判空
public boolean isEmpty() {
return size == 0;
}
@Override
// 链表的大小
public int size() {
return size;
}
@Override
// 置空
public void clear() {
head = null;
tail = null;
size = 0;
}
@Override
// 获取元素
public T get(int index) {
if(index < 0 || index > size){
throw new IndexOutOfBoundsException("Index out of bound!");
}
Node p = head;
int count = 1;
while(p != null && count < index){
p = p.next;
count++;
}
if(p == null || count > size){
System.out.println("第" + index + "个元素不存在!");
}
return (T)p.data;
}
@Override
// 添加元素(默认添加至表尾)
public void add(T element) {
addLast(element);
}
@Override
// 链表头部插入元素(头插法)
public void addFirst(T element) {
Node node = new Node(element, null);
node.next = head;
head = node;
// 若插入前是空表
if(tail == null){
tail = head;
}
size++;
}
@Override
// 链表尾部添加元素,要先判断是否为空表,
public void addLast(T element) {
// 如果链表为空,那么头指针还是null,所以处理一下头指针
if(head == null){
head = new Node(element, null);
tail = head;// 这样处理之后,head和tial都指向同一个节点
}else{
Node node = new Node(element, null);
tail.next = node;
tail = node;// tail重新指向尾结点
}
size++;
}
@Override
// 指定位置添加元素
public void add(int index, T element) {
int count = 1;
Node p = head;
Node s = new Node();// 给要插入的元素创建节点
while(p != null && count < index){
p = p.next;
count++;
}
if(p == null || count > index){
System.out.println("不能插入!");
}
s.data = element;
s.next = p.next;
p.next = s;
size++;
}
@Override
// 移除元素
public void remove(T element) {
Node p = head;
// 注意:要使p指向要删除元素对应结点的前一个结点
while(p != null && !p.next.data.equals(element)){
p = p.next;
}
if(p == null){
System.out.println("元素不存在!");
}
p.next = p.next.next;
}
@Override
// 遍历
public void display() {
Node node = head;
while(node != null){
System.out.print(node.data + " ");
node = node.next;
}
System.out.println();
}
}
每个结点包含一个数据域和两个指针域,两个指针域分别保存对下一个结点和前一个结点的引用。
public class DoubleLinkList {
// 节点类
private class Node{
public Object data;// 数据域
public Node next;// 后继指针域
public Node pre;// 前驱指针域
public Node(){
this.next = null;
this.pre = null;
}
public Node(Object data){
this();
this.data = data;
}
}
private Node head;// 头结点
private Node tail;// 尾结点
private int size = 0;// 保存已有的结点数
public DoubleLinkList(){
head = null;
tail = null;
}
// 判空
public boolean isEmpty(){
return size == 0;
}
// 链表头部添加结点
public void addHead(Object data){
Node node = new Node(data);// 为要添加的数据创建一个新结点
if(head == null){// 链表为空,设置尾结点为新添加的结点
tail = node;
}else{
head.pre = node;// 新结点作为当前头结点的前驱
node.next = head;// 当前头结点作为新结点的后继
}
head = node;
size++;
}
// 删除头结点
public void deleteHead(){
if(isEmpty()){
System.out.println("链表为空!");
}else{
Node node = head;
head = node.next;
head.pre = null;
}
size--;
}
// 链表尾部添加结点
public void addTail(Object data){
Node node = new Node(data);
if(head == null){
head = node;
}else{
tail.next = node;
node.pre = tail;
}
tail = node;
size++;
}
// 删除尾结点
public void deleteTail(){
if(isEmpty()){
System.out.println("链表为空!");
}
else{
Node node = tail;
tail = node.pre;
tail.next = null;
}
size--;
}
// 查找(根据数据域查找)
public Node find(Object data){
Node node = head;
while(!node.data.equals(data)){
if(node.next == null){// 遍历至表尾仍为空,返回null
return null;
}
node = node.next;
}
return node;
}
// 删除(根据数据域删除)
public Node delete(Object data){
Node node = head;
while(!node.data.equals(data)){
if(node.next == null){// 遍历至表尾仍为空,返回null
return null;
}
node = node.next;
}
if(node == head){
head = node.next;
}else{
node.pre.next = node.next;
}
return node;
}
// 遍历
public void display(){
Node node = head;
while(node != null){
System.out.print(node.data + " ");
node = node.next;
}
System.out.println();
}
public static void main(String[] args) {
DoubleLinkList list = new DoubleLinkList();
list.addHead(1);
list.addHead(2);
list.addHead(3);
list.addTail(4);
list.addTail(5);
list.addTail(6);
list.display();
list.deleteHead();
list.display();
list.deleteTail();
list.display();
list.delete(4);
list.display();
}
}
运行结果:
3 2 1 4 5 6
2 1 4 5 6
2 1 4 5
2 1 5