线性表是其组成元素间具有线性关系的一种线性结构,是由n个数据类型相同的元素构成的有限序列。其具有“一对一”的逻辑关系,与位置有关,除了头尾元素之外,每一个元素都有唯一的前驱元素和后继元素,即元素ai前面的元素为ai-1,后面的元素为ai+1。
线性表的抽象数据类型可以用接口描述,其中列举了一些基本的功能,可以自己根据需求扩充:
package ch02;
public interface IList {
public void insert(int index,Object data) throws Exception; //在指定位置插入元素
public void clear(); //清空线性表
public void remove(int index); //删除指定位置元素
public boolean isEmpty(); //判断线性表是否为空
public Object get(int index); //获取指定位置元素
public int length(); //获取线性表长度
public int indexOf(Object data); //获取指定元素的角标
public void display(); //输出线性表中所有元素
}
线性表的顺序存储结构,是把线性表中的所有元素按照其逻辑顺序,依次存储到计算机在内存中指定的一块连续存储空间中,成为顺序表。
元素在内存中的物理存储位置和他们在线性表中的逻辑位置一致。
常使用数组作为顺序表的底层数据结构进行存储。
package ch02;
public class SqList implements IList {
private Object[] listItem; //顺序表的存储空间大小;
private int curLen; //顺序表的当前长度
private int maxSize; //顺序表的最大尺寸
//构造最大尺寸为maxSize的顺序表
SqList(int maxSize){
this.maxSize = maxSize;
this.curLen = 0;
this.listItem = new Object[maxSize];
}
//在第index的位置插入数据data
public void insert(int index, Object data) throws Exception{
if(curLen == maxSize) //判断存储空间是否已满
throw new Exception("数组已满,无法插入!");
if(index<0||index>curLen) //判断插入位置是否合法
throw new IndexOutOfBoundsException("所插入的位置不在索引范围!");
for(int i=curLen;i>index;i--) { //将插入位置后面的所有元素后移一位
listItem[i]=listItem[i-1];
}
listItem[index] = data; //插入数据
curLen++; //表长+1
}
//清空顺序表
public void clear() {
curLen = 0;
}
//删除顺序表中指定位置index处的数据
public void remove(int index) throws IndexOutOfBoundsException{
if(index<0||index>curLen) //判断位置是否合法
throw new IndexOutOfBoundsException("当前索引不存在!");
for(int i=index;icurLen)
throw new IndexOutOfBoundsException("当前索引不存在!");
return listItem[index];
}
//返回顺序表长度
public int length() {
return curLen;
}
//获取元素在顺序表中的位置
public int indexOf(Object data) {
for(int i=0;i
优点:存取元素快;
缺点:插入和删除元素慢,且需要预先分配空间,容易造成空间不足或空间浪费
采用链式存储结构的线性表成为链表,是用若干地址分散的存储单元存储数据元素,逻辑上相邻的数据元素在物理位置上不一定相邻,因此,链表在数据结构上分为两部分,一部分存储数据,成为数据域;一部分存储下一个元素的内存地址,成为指针域。
单链表是指节点中只包含一个指针域的链表,指针域存储指向后继结点的指针。头指针为单链表的起始地址,为单链表的唯一标识。尾指针没有后继结点,其后继结点为null,可以作为单链表结束的判定标识。
为方便操作,一般会有一个头结点,头指针的数据域为空,指针域指向第一个结点,当头结点的指针域为null是位空表。
单链表结点的存储空间是在插入和删除的过程中动态生成和释放的,不需要预先分配空间,从而避免了空间分配的不足和过剩。同时,单链表在插入和删除节点时不需要移动数据,所以其插入和删除效率较高。
package ch02;
public class Node {
public Object data; //数据域
public Node next; //指针域
public Node(){
this(null,null);
}
public Node(Object data){
this(data,null);
}
public Node(Object data,Node next){
this.data = data;
this.next = next;
}
}
package ch02;
import java.util.Scanner;
public class LinkList implements IList{
public Node head; //单链表头指针
public LinkList() {
head = new Node();
}
public LinkList(int n,boolean order) {
this();
if(order)
creat1(n);
else
creat2(n);
}
//头插法——读入的数据顺序与结点顺序相反
public void creat1(int n) {
Scanner sc = new Scanner(System.in);
for(int i=0;iindex-1||preNode==null) //操作不合法则抛出异常
throw new Exception("非法操作!");
Node newNode = new Node(data); //创建新插入的结点对象
newNode.next = preNode.next; //更新指针域指向
preNode.next = newNode;
}
//清空单链表
public void clear() {
head.data = null;
head.next = null;
}
//删除节点
public void remove(int index) throws IndexOutOfBoundsException{
Node preNode = head;
int preIndex;
for(preIndex=-1;preIndexindex-1||preNode==null) //操作不合法则抛出异常
throw new IndexOutOfBoundsException(“角标越界了!”);
preNode.next = preNode.next.next;
}
//判断单链表是否为空
public boolean isEmpty() {
return head.next == null;
}
//获取位置index处的数据
public Object get(int index) throws IndexOutOfBoundsException {
Node nextNode = head.next;
int j;
for(j=0;jindex||nextNode==null) //操作不合法则抛出异常
throw new IndexOutOfBoundsException("元素不存在!");
return nextNode.data;
}
//获取单链表长度
public int length() {
Node nextNode = head.next;
int length = 0;
while(nextNode!=null) {
nextNode = nextNode.next;
length++;
}
return length;
}
//按值查找对应的角标
public int indexOf(Object data) {
Node nextNode = head.next;
int index;
for(index=0;nextNode!=null;index++) {
if(nextNode.data.equals(data))
return index;
}
return -1;
}
@Override
public void display() {
// TODO Auto-generated method stub
}
}
单链表插入时,只需要找到所插入位置结点的前驱结点,然后修改前驱和新插入节点的指针域即可。
循环链表就是在单链表的基础上修改而来,只是将尾结点的指针域指向了头结点,从而形成一个环状的链表。
在实现循环链表时可以用头指针或尾指针或两者同时使用来标识循环链表,通常使用尾指针来标识,可简化操作。
由于循环链表和单链表操作算法基本一致,所以不再赘述。
双向链表即在单链表的基础上又添加了一个指针域,用来指向其前驱结点,使得在查找某个结点的前驱结点时不再需要从表头开始顺次查找,大大减小了事件复杂度,但相应的会增加内存的空间消耗。这里只写出比较核心的插入节点算法,其他算法可以参照单链表的算法自行写出。
public void insert(int i, Object x) throws Exception{
DulNode p = head; //这里没有写双向链表的节点类,读者可自行补全
int j = -1;
while(p!=null&&ji || p==null)
throw new Exception("插入位置不合法!");
DulBode s = new DulNode(x);
p.prior.next = s;
s.next = p;
p.prior = s;
}
顺序表 | 链表 | |
---|---|---|
优点 | 1. 存储高效 2. 存储密度高,空间开销小 3. 实现简单,易于使用 | 1.灵活,可进行空间的动态分配 2. 插入、删除效率高 |
缺点 | 1. 需要预先分配空间; 2. 插入、删除操作时间复杂度高 | 1.存储密度低; 2. 不存在角标,查找元素时间复杂度高 |