1.什么是线性表(List)?
零个或多个数据元素的有限序列。
(1)元素之间是有序的。
(2)线性表强调是有限的。
2.线性表有哪些操作?
(1)线性表的创建和初始化,InitList
(2)判空,ListEmpty(),true-空
(3)线性表重置为空表,ClearList()
(4)获取线性表第i个位置的值,GetElem(int i)
(5)查找线性表中值为x的结点 ,LocateElem(int x)
(6)在线性表中第i个位置插入值为x的结点 , InsertElem(int x,int i)
(7)删除线性表的第i个结点 ,DeleteElem(int i)
(8)求线性表的长度 ,ListLength()
(9)判断线性表是否已满 ,ListFull()
3.线性表的两种物理结构:顺序存储结构和链式存储结构
(1)顺序存储结构
三个属性:
存储空间的起始位置:数组data
线性表的最大存储容量:数组长度MaxSize
线性表的当前长度:length
注意:数组长度和线性表长度的区别
数组长度一般存储分配后就不变了。
线性表长度是变化的,因为删除和插入的操作。
所以线性表的长度应小于等于数组的长度
线性表的顺序存储结构,在存,读取数据时,不管是哪个位置,时间复杂度都是O(1),插入和删除元素,时间复杂度都是O(n).
public class LinearList { private int[] data; //线性表以数组形式存放 private int MaxSize;//表空间的最大范围 private int Last; //表当前结点个数,即表长 //构造函数 public LinearList(int MaxSize) { if(MaxSize>0){ this.MaxSize = MaxSize; Last = 0; //创建表空间 data = new int [MaxSize]; } } //判断线性表是否为空 public boolean ListEmpty(){ return (Last <=0)?true:false; } //判断线性表是否已满 public boolean ListFull(){ return(Last >= MaxSize)?true:false; } //求线性表的长度 public int ListLength(){ return Last; } //求线性表中第i个结点的值 public int GetElem(int i){ //若存在,返回结点,否则,返回null; return(i<0||i>Last)?null:data[i]; } //查找线性表中值为x的结点 public int LocateElem(int x){ //查找表中值为x的结点,找到则返回该结点的序号;否则返回-1; //若表中值为x的结点有多个,找到的是最前面的一个; for(int i=0;i<Last;i++){ if(data[i] == x)return i; } return -1; } //在线性表中第i个位置插入值为x的结点 public boolean InsertElem(int x,int i){ //在表中第i个位置插入值为x的结点 //若插入成功,则返回true,否则返回false //插入位置不合理,插入失败 if(i<0||i>Last||Last == MaxSize) return false; else { //后移 for(int j = Last;j>i;j--) data[j] = data[j-1]; //插入 data[i] = x; //表长增一 Last++; return true; } } //删除线性表的第i个结点 public boolean DeleteElem(int i){ //删除表中第i个结点,若成功,返回true;否则返回false; //第i个结点不存在,删除失败; if(i<0||i>=Last||Last == 0) return false; else { //前移 for(int j=i;j<Last-1;j++) data[j] = data[j+1]; //表长减1 Last--; return true; } } public void display(){ System.out.println("当前链表长度:"+ ListLength()); for(int i=0;i<Last;i++) { System.out.println("第"+i+"结点为:"+data[i]); } }
总结:线性表顺序存储结构的优缺点
优点:可以快速地存和取表中任一位置的元素
缺点:插入和删除操作需要移动大量元素
(2)线性表链式存储结构
结点(Node)由两部分组成:存放数据元素的数据域+存放后继结点地址的指针域
/* * 查找方法 * */ public Node find(long value){ Node current=first;//让current指向链表的第一个结点 while(current.data!=value){ //如果data不等于value,就一直往下面遍历。 if(current.next==null){ //若到了链表末尾,则没有找到,返回null return null; } current=current.next; //否则,不断往下个结点移动 } return current; //当找到data等于value,则返回current }
栗子:
链表不具有的特点是(B)
A.插入、删除不需要移动元素
B.可随机访问任一元素(错误,因为要访问一个元素,链表头指针只能通过链表元素的指针域前或向后移动,所以并不能随机访问任意元素。)
C.不必事先估计存储空间
D.所需空间与线性长度成正比
单链表第 i 个数据插入结点的算法思路:(先遍历查找第 i 个结点,再插入)
(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,作为返回
(7)释放q结点;
(8)返回成功.
其实,1.删除可以归结为一步:p->next=p->next->next;
2.单链表插入和删除,都是由两部分组成:
遍历查找第i个结点,
插入和删除结点,
最坏的情况的时间复杂度都是O(n)
栗子:头插法整表创建和在头结点处进行删除结点
Node package cn.itcast; /* * 链结点,相当于是车厢 * */ public class Node { //数据域 public long data; //结点域(指针域):用来保存下一个节点的引用,初始化是null public Node next; public Node(long value){ this.data=value; } /* * 显示方法 * */ public void display(){ System.out.print(data+" "); } } LinkList package cn.itcast; /* * 链表,相当于火车 * */ public class LinkList { //头结点 private Node first; //默认构造函数 public LinkList(){ first=null; } /* * 插入一个结点,在头结点后进行插入,头指针是链表中第一个结点的存储位置,而头指针是在头结点中的,头结点的数据域可以不存储任信息,也可以存储如线性表的长度的附加信息。头结点的指针与存储指向第一个结点的指针 * */ public void insertFirst(long value){ Node node=new Node(value); node.next=first; //将first作为node的后继结点 first=node; //将当前新定义的node作为第一个结点,本来 first是第一个结点,可现在已经不是第一个结点了,现在第一个结点是 node。所以应该要让将node结点这个第一个结点赋值给first.此时,first又是第一个结点了。 } /* * 删除一个结点,在头结点后进行删除;单链表的第一个结点前附设一个结点,就是头结点 * */ public Node deleteFirst(){ Node tmp=first; first=tmp.next; return tmp; } /* * 显示方法*/ public void display() { Node current=first; while(current!=null){ current.display(); current=current.next; } System.out.println(); } /* * 查找方法 * */ public Node find(long value){ Node current=first;//让current指向链表的第一个结点 while(current.data!=value){ //如果data不等于value,就一直往下面遍历。 if(current.next==null){ //若到了链表末尾,则没有找到,返回null return null; } current=current.next; //否则,不断往下个结点移动 } return current; //当找到data等于value,则返回current } /* * 删除方法,根据数据域来进行删除 * */ public Node delete(long value){ Node current=first; Node previous=first; while(current.data!=value){ if(current.next==null){ return null; }else{ previous=current; current=current.next; //将下一结点给current } } if(current==first){ first=first.next; //如果是第一个结点,就把第一个结点的后继结点作为第一个结点 }else{ previous.next=current.next;//把当前结点current的后继结点(current.next)作为previous的后继结点(previous.next) } return current; } } TestLinkList package cn.itcast; public class TestLinkList { public static void main(String[] args) { LinkList linklist=new LinkList(); linklist.insertFirst(34); linklist.insertFirst(23); linklist.insertFirst(12); linklist.insertFirst(1); linklist.insertFirst(51); linklist.insertFirst(47); linklist.display(); linklist.deleteFirst();//删除第一个结点,值value为47 linklist.display(); Node node=linklist.find(51);//查找value是51的 node.display(); System.out.println(); Node node1=linklist.delete(1);//删除值为1的 node1.display(); } }
总结:单链表适合插入和删除操作,时间复杂度O(1)。而查找则是O(n)
顺序存储结构适合查找,时间复杂度O(1)。而插入和删除操作则是O(n)(为什么适合查找?因为顺序用的是数组,只要data[i]就是想要查的元素)
栗子:
若某线性表最常用得操作是存取任一指定序号的元素和在最后进行插入和删除运算,则利用哪种存储方式最节省时间?A
A.顺序表
B.双链表
C.带头结点的双循环链表
D.单循环链表