线性表
线性表指的是具有相同类型的n(n>=0)个数据元素组成的有限序列。通常有顺序存储和链式存储两种实现方式。
顺序存储通常用数组来实现,即占用一段连续的内存空间来关联数据的先后顺序。链式存储通常用指针来实现,即需要保存两个空间数据域和指针域的信息,用指针来关联线性表不同元素的内存地址,换句话来说,链式存储的数据不必保存在连续的内存空间中,指针描述了数据的先后顺序。
优缺点:
查找,顺序结构时间复杂度O(1)远低于链式结构O(n)
增加和删除,链式结构的时间复杂度O(1)低于顺序结构(最坏是O(n))
顺序存储实现SequenceList:
package SequenceList; //顺序表 public class SqList<T>{ private static final int DefaultSize=10; //初始化默认数组大小为10 public int maxLength; //maxLength代表数组容量,即开辟了内存空间的数组大小 public int size; //size代表数组中存入的有效数据 public Object[] element; //数据元素 public SqList(T[] values) { maxLength=DefaultSize; //保存数组容量 element=new Object[DefaultSize]; //创建数组对象 for(int i=0;i<values.length;i++) { element[i]=values[i]; } size=values.length; } //遍历 public void traversal() { for(int i=0;i<size;i++) { System.out.println(element[i]); } } //判断是否为空 public boolean isEmpty() { return size==0; } //判断数组容量是否装满了 public boolean isFull() { return size==maxLength; } //在第index位置后插入x,O(n) public int insert(int index,T x) { if(index<=0) { index=0; } if(index>maxLength-1) { index=maxLength; } if(isFull()) //判断是否数组是否装满了 { Object[] source=new Object[maxLength]; //扩大数组容量为当前容量的两倍 for(int i=0;i<size;i++) { source[i]=element[i]; } maxLength=maxLength*2; System.out.println("容器已满,maxLength扩大为:"+maxLength); element=new Object[maxLength]; for(int i=0;i<size;i++) { element[i]=source[i]; } } for(int i=size;i>index;i--) { element[i]=element[i-1]; } element[index]=x; size++; return index; } //在第index位置删除元素,O(n) public void deleteElem(int index) { for(int i=index;i<size-1;i++) { element[i]=element[i+1]; } size--; } //设置第index位置的元素 public void set(int index,T x) { element[index]=x; } //获取第index位置的元素 public T get(int index) { return (T)element[index]; } //清空 public void clear() { size=0; } }
测试程序:
package SequenceList; public class App { public static void main(String[] args) { // TODO Auto-generated method stub Integer[] a={1,2,3,4,5,6,7,8,9,10}; SqList<Integer> sqList=new SqList<>(a); //初始化顺序表 System.out.println("当前maxLength:"+sqList.maxLength); System.out.println("当前size:"+sqList.size); System.out.println("插入前遍历"); sqList.traversal(); sqList.insert(10, 100); //在第10个元素后插入100,数组容量超出 System.out.println("插入后,当前maxLength:"+sqList.maxLength); System.out.println("插入后,当前size:"+sqList.size); System.out.println("插入后遍历"); sqList.traversal(); } }
结果:
当前maxLength:10 当前size:10 插入前遍历 1 2 3 4 5 6 7 8 9 10 容器已满,maxLength扩大为:20 插入后,当前maxLength:20 插入后,当前size:11 插入后遍历 1 2 3 4 5 6 7 8 9 10 100
在早期的编程语言中,没有C语言的指针,也没有其他高级语言的对象引用机制,聪明的人想到仍然可以用数组实现链式存储,即保存一个“伪指针”来指向数组的下标来实现指针的功能。
这就是静态链表,第一个结点和最后一个结点都不保存数据,只保存指针。其中第一个结点用来指向备用链表的第一个空间的坐标(即未保存数据的空间), 最后的结点(下标为Length-1的位置)用来指向第一个插入元素位置的坐标,充当头结点(即图中下标为1的位置)。
静态链表的实现StaticLinkedList:
package StaticLinkedList; import org.junit.Test; //静态链表 public class SLList{ private static final int DefaultSize=100;//初始化100个空间 public element[] link=null; public int current=0; //备用链表坐标 public int head=0; //头结点坐标 public int length=0;//链表长度 public int Maxlength=0; //初始化 public SLList() { Maxlength=DefaultSize; link=new element[DefaultSize]; for(int i=0;i<20;i++) //初始化有用链表的空间 { link[i]=new element(); link[i].setCur(i+1); link[i].setData(i);; } link[20]=new element(); link[20].setData(link[19].getData()+1); link[20].setCur(0); for(int i=21;i<Maxlength-1;i++) //初始化备用链表的空间 { link[i]=new element(); link[i].setCur(i+1); } length=20; link[0].setCur(21); //第一个元素指向备用链表的第一个空间 link[Maxlength-1]=new element(); link[Maxlength-1].setCur(1); // } //遍历 public void traversal() { getElementTraversal(link[Maxlength-1]); } //递归遍历 public void getElementTraversal(element e) { if(e.getCur()==0) { return; } else { System.out.println(link[e.getCur()].getData()); getElementTraversal(link[e.getCur()]); } } //获取链表第index个元素,递归访问 public element get(int index) { return link[getElementCur(index,link[Maxlength-1])]; } public int getElementCur(int index,element e) { if(index-1==0) { return e.getCur(); } else { index--; return getElementCur(index, link[e.getCur()]); } } //在第index元素后增加元素 public void addElement(int indx,int data) { //新增元素地址cur1 int cur1=link[0].getCur(); link[cur1]=new element(); link[cur1].setData(data); //新增元素Cur指向index后的元素 link[cur1].setCur(get(indx).getCur()); //index元素指向新增元素 get(indx).setCur(link[0].getCur()); //link[0]指向备用链表为空的位置 link[0].setCur(link[0].getCur()+1);; } } class element{ private int data; private int cur; public int getData() { return data; } public void setData(int data) { this.data = data; } public int getCur() { return cur; } public void setCur(int cur) { this.cur = cur; } }
测试程序:
package StaticLinkedList; public class App { public static void main(String[] args) { // TODO Auto-generated method stub SLList slList=new SLList(); System.out.println("插入前遍历"); slList.traversal(); element e=slList.get(3); System.out.println("查找第3个位置的元素"); System.out.println("cur:"+e.getCur()+" data:"+e.getData()); slList.addElement(2,100); System.out.println("插入后遍历"); slList.traversal(); } }
结果:
插入前遍历 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 查找第3个位置的元素 cur:4 data:3 插入后遍历 1 2 100 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
在C语言出现之后,指针能比较方便的描述链式结构。链式结构为了方便一些查找和遍历的操作,可以保存头指针和尾指针来辅助。链式结构分为单向链表和双向链表,单向链表通常一个结点只保存下一个对象的指针。双向则同时保存上个结点和下个结点的指针。同时,多个链表也能组成循环链表,比如将第一个链表的尾节点指向第二个链表的头结点,第二个链表最后的结点指向第一个链表的头结点,这样就形成了一个封闭的环结构。
单链表的插入通常有头插法和尾插法两种方式,头插法的头结点永远指向链表的新增结点上,即新增结点在链表的首位。当然这样的插入方式在遍历时会发现先插入的结点后输出,是一种栈结构。
而通常符合我们的思维的是新插入结点排在上个插入结点的后面,这种插入方式称为尾插法,可以保存一个尾节点指针来插入数据。
单链表的实现:
package SingleLinkedList; import java.util.Random; //链式存储 单链表 public class SLList{ public SLList() { head=new SLListNode<Integer>(null); tail=new SLListNode<Integer>(null); tailInsertInit(); } //头指针 public SLListNode<Integer> head; //尾指针 public SLListNode<Integer> tail; //节点数量 public int length; //遍历 public void traversal() { if(head!=null) { SLListNode<Integer> tempNode=head; while(tempNode.next!=null) { tempNode=tempNode.next; System.out.println(tempNode.data); } } else { return; } } //头插法初始化 public SLListNode<Integer> headInsertInit() { Random random=new Random(); for(int i=0;i<20;i++) { int temp=random.nextInt(100); SLListNode<Integer> newNode=new SLListNode<>(i); length++; if(head==null) { head.next=newNode; } else { newNode.next=head.next; head.next=newNode; } } return head; } //头插法 public SLListNode<Integer> headInsert(SLListNode<Integer> Node) { if(head!=null) { Node.next=head.next; //插入结点的指针指向头结点指向的结点 head.next=Node; //头结点指向插入结点,始终保持指向新插入的结点 } else { head.next=Node; } length++; return head; } //尾插法初始化 public SLListNode<Integer> tailInsertInit() { Random random=new Random(); for(int i=0;i<20;i++) { int temp=random.nextInt(100); SLListNode<Integer> newNode=new SLListNode<>(i); length++; if(head.next==null) { head.next=newNode; tail.next=newNode; } else { tail=tail.next; tail.next=newNode; } } return head; } //尾插法 public SLListNode<Integer> tailInsert(SLListNode<Integer> Node) { if(head.next!=null) { tail=tail.next; //找到尾指针 tail.next=Node; //尾指针指向新增结点 } else { head.next=Node; tail.next=Node; } length++; return head; } //在第index个结点后插入x public int insertByIndex(int index,SLListNode<Integer> x) { if(index<=0) { x.next=head.next; head.next=x; } int i=0; SLListNode<Integer> tempNode=head; while(i<index) { tempNode=tempNode.next; i++; } x.next=tempNode.next; tempNode.next=x; return index; } //删除第index个结点 public SLListNode<Integer> deleteByIndex(int index) { if(index<=0) { head.next=head.next.next; } int i=0; SLListNode<Integer> tempNode=head; while(i<index-1) { tempNode=tempNode.next; i++; } tempNode.next=tempNode.next.next; return head; } } //单链表 class SLListNode<T>{ public T data; public SLListNode<T> next; public SLListNode(T data) { this.data=data; } }
测试:
package SingleLinkedList; public class App { public static void main(String[] args) { // TODO Auto-generated method stub SLList slList=new SLList(); //使用尾插法 System.out.println("插入前遍历"); slList.traversal(); slList.insertByIndex(2, new SLListNode<Integer>(200)); System.out.println("插入后遍历"); slList.traversal(); } }
结果:
插入前遍历 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 插入后遍历 0 1 200 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19