一、链表类型
二、单链表结构根据链表的构造方式的不同可以分为:
1.单向链表
2.单向循环链表
3.双向循环链表
单链表是构成链表的每个结点只有一个指向直接后继结点的指针。
单链表的表示方法:
单链表中每个结点的结构:
单链表有带头结点结构和不带头结点结构两种。我们把指向单链表的指针称为单链表的头指针。头指针所指的不存放数据元素的第一个结点称作头结点。存放第一个数据元素的结点称作第一个数据元素结点,或称首元结点。
从线性表的定义可知,线性表要求允许在任意位置进行插入和删除。当选用带头结点的单链表时,插入和删除操作的实现方法比不用带头结点单链表的实现方法简单。(a) 空链 (b)非空链
设头指针用head表示,在单链表中任意结点(但不是第一个数据元素结点)前插入一个新结点的方法如图2-6所示。算法实现时,首先把插入位置定位在要插入结点的前一个结点位置,然后把s表示的新结点插入单链表中。
在单链表非第一个结点前插入结点过程
要在第一个数据元素结点前插入一个新结点,若采用不带头结点的单链表结构,则结点插入后,头指针head就要等于新插入结点s,这和在非第一个数据元素结点前插入结点时的情况不同。另外,还有一些不同情况需要考虑。因此,算法对这两种情况就要分别设计实现方法。
而如果采用带头结点的单链表结构,算法实现时,p指向头结点,改变的是p指针的next指针的值,而头指针head的值不变。因此,算法实现方法比较简单。在带头结点单链表中第一个数据元素结点前插入一个新结点的过程如图所示。
带头结点单链表第一个结点前插入结点过程
三、节点类
单链表是由一个一个结点组成的,因此,要设计单链表类,必须先设计结点类。结点类的成员变量有两个:一个是数据元素,另一个是表示下一个结点的对象引用(即指针)。
设计操作:
1.头结点的初始化
2.非头结点的构造
3.获取该结点指向的下个结点
4.设置该结点指向的下个结点
5.设置该结点的数据
6.获取该结点的数据
四、单链表类
单链表类的成员变量至少要有两个:一个是头指针,另一个是单链表中的数据元素个数。但是,如果再增加一个表示单链表当前结点位置的成员变量,则有些成员函数的设计将更加方便。
五、实现代码
public interface List { //插入元素 public void add(int index,Object obj) throws Exception; //删除元素 public void delete(int index) throws Exception; //获取某个元素 public Object get(int index) throws Exception; //判断是否为空 public boolean isEmpty(); //获得集合的长度 public int size(); }
//节点类 public class Node { //数据域 Object element; //指针域 Node next; //头结点的构造方法 public Node(Node nextval) { this.next=nextval; } //非头结点的额构造方法 public Node(Object obj,Node nextval) { this.element=obj; this.next=nextval; } //获取当前数据域的值 public Object getElement() { return this.element; } //获取当前节点的后继节点 public Node getNode() { return this.next; } //设置当前数据域的值 public void setElement(Object obj) { this.element=obj; } //设置当前节点的指针域 public void setNode(Node node) { this.next=node; } public String toString() { return this.element.toString(); } }
public class LinkedList implements List { //头指针 private Node head; //当前节点对象 private Node current; //节点个数 int size; public LinkedList() { //初始化头结点,让头指针指向头结点,并且让当前节点对象等于头结点 this.head=current=new Node(null); this.size=0; } //定为函数,将当前节点对象定为到前一个节点 public void index(int index) throws Exception { if(index<-1||index>size-1) { throw new Exception("参数错误!"); } //说明在头结点之后操作 if(index==-1) return; current=head.next; //循环变量 int j=0; while(current!=null&&j<index) { current=current.next; j++; } } //增加元素 @Override public void add(int index, Object obj) throws Exception { if(index<0||index>size) { throw new Exception("增加元素参数错误!"); } //定为到操作对象的前一个对象 index(index-1); current.setNode(new Node(obj,current.next)); size++; } //删除元素 @Override public void delete(int index) throws Exception { if(isEmpty()) { throw new Exception("链表为空,删除错误!"); } if(index<0||index>size) { throw new Exception("删除元素参数错误!"); } index(index-1); current.setNode(current.next.next); size--; } @Override public Object get(int index) throws Exception { if(index<-1||index>size-1) { throw new Exception("参数错误!"); } index(index); return current.getElement(); } @Override public boolean isEmpty() { return size==0; } @Override public int size() { return size; } }//测试类public class Main { public static void main(String[] args) throws Exception { LinkedList list=new LinkedList(); Random random=new Random(); for (int i = 0; i < 10; i++) { int temp=random.nextInt(100); list.add(i, temp); System.out.print(temp+" "); } System.out.println(); for(int j=0;j<list.size();j++) { System.out.print(list.get(j)+" "); } System.out.println(); list.delete(5); for(int j=0;j<list.size();j++) { System.out.print(list.get(j)+" "); } } }
结果:
六、单链表的效率分析
在单链表的任何位置上插入数据元素的概率相等时,在单链表中插入一个数据元素时比较数据元素的平均次数为:
删除单链表的一个数据元素时比较数据元素的平均次数为:
因此,单链表插入和删除操作的时间复杂度均为O(n)。另外,单链表取数据元素操作的时间复杂度也为O(n)。
七、顺序表和单链表的比较
顺序表的主要优点是支持随机读取,以及内存空间利用效率高;顺序表的主要缺点是需要预先给出数组的最大数据元素个数,而这通常很难准确作到。当实际的数据元素个数超过了预先给出的个数,会发生异常。另外,顺序表插入和删除操作时需要移动较多的数据元素。和顺序表相比,单链表的主要优点是不需要预先给出数据元素的最大个数。另外,单链表插入和删除操作时不需要移动数据元素。
单链表的主要缺点是每个结点中要有一个指针,因此单链表的空间利用率略低于顺序表的。另外,单链表不支持随机读取,单链表取数据元素操作的时间复杂度为O(n);而顺序表支持随机读取,顺序表取数据元素操作的时间复杂度为O(1)。