一、链表
概念:
链式存储结构是基于指针实现的。我们把一个数据元素和一个指针称为结点。
数据域:存储数据元素信息的域。
指针域:存储直接后继位置的域。
1.链式存储结构是用指针把相互直接关联的结点(即直接前驱结点或直接后继结点)链接起来。链式存储结构的线性表称为链表。
2.单链表:链表的每个结点中只包含一个指针域,叫做单链表(即构成链表的每个结点只有一个指向直接后继结点的指针)
a.单链表的操作:
1.添加:上图可以看出 单向链表只有一个指向,原来head为p, p指向s, 添加结点只需要把p指向q, q指向s就可以了,即:p--->q; q--->s;这样就实现了单向链表的参加;
2.删除:原理与添加相反,若此时链表为p--->q--->s;若删除q结点只需要更改p的指向就可以了 p--->s,这样就删掉了;
3.查找:查找操作需要对整个单链表进行遍历,直到满足查找条件为止;
4.修改:此操作一般建立在查找之上进行,找到结点之后对值进行修改;
b.头指针和头结点:
单链表有带头结点结构和不带头结点结构两种。
"链表中第一个结点的存储位置叫做头指针",如果链表有头结点,那么头指针就是指向头结点的指针。
头指针所指的不存放数据元素的第一个结点称作头结点(头结点指向首元结点)。头结点的数据域一般不放数据(当然有些情况下也可存放链表的长度,用做监视哨等)
存放第一个数据元素的结点称作第一个数据元素结点,或称首元结点。
如下图所示:
c.不带头结点的单链表如下:
d.带头结点的单链表如下:
e.不带头结点的单链表的插入操作:
上图中,是不带头结点的单链表的插入操作。如果我们在非第一个结点前进行插入操作,只需要a(i-1)的指针域指向s,然后将s的指针域指向a(i)就行了;
如果我们在第一个结点前进行插入操作,头指针head就要等于新插入结点s,这和在非第一个数据元素结点前插入结点时的情况不同。另外,还有一些不同情况需要考虑。因此,算法对这两种情况就要分别设计实现方法。
f.带头结点的单链表的插入操作:(操作统一,推荐)
上图中,如果采用带头结点的单链表结构,算法实现时,p指向头结点,改变的是p指针的next指针的值(改变头结点的指针域),而头指针head的值不变。因此,算法实现方法比较简单,其操作与对其他结点的操作统一。
问题1:头结点的好处:
头结点即在链表的首元结点之前附设的一个结点,该结点的数据域中不存储线性表的数据元素,其作用是为了对链表进行操作时,可以对空表,非空表的情况以及对首元结点进行统一处理,编程更方便。
问题2:头结点的数据域内装的是什么?
头结点的数据域可以为空,也可存放线性表长度等附加信息,但此结点不能计入链表长度值。
有头结点时,当头结点的指针域为空时表示空表。
二、单项链表的代码实现:
1.结点类:
单链表是由一个一个结点组成的,因此,要设计单链表类,必须先设计结点类。结点类的成员变量有两个:一个是数据元素,另一个是表示下一个结点的对象引用(即指针)。
步骤如下:
(1)头结点的构造(设置指针域即可)
(2)非头结点的构造
(3)获得当前结点的指针域
(4)获得当前结点数据域的值
(5)设置当前结点的指针域
(6)设置当前结点数据域的值
注:类似于get和set方法,成员变量是数据域和指针域。
2.代码实现:
(1)List.java:(链表本身也是线性表,只不过物理存储上不连续)
(2)Node.java:结点类
2.单链表类:
单链表类的成员变量至少要有两个:一个是头指针,另一个是单链表中的数据元素个数。但是,如果在增加一个表示单链表当前结点位置的成员变量,则有些成员函数的设计将更加方便。
代码实现:
(3)LinkList.java:单项链表类(核心代码)
3.测试类:(单链表的应用)
使用单链表建立一个,依次输入十个0-99之间的随机数,删除第5个元素,打印输出该线性表。
(4)Test.java:
三、双链表
概念:
链式存储结构是基于指针实现的。我们把一个数据元素和一个指针称为结点。
数据域:存储数据元素信息的域。
指针域:存储直接后继位置的域。
1.链式存储结构是用指针把相互直接关联的结点(即直接前驱结点或直接后继结点)链接起来。链式存储结构的线性表称为链表。
2.单链表:链表的每个结点中只包含一个指针域,叫做单链表(即构成链表的每个结点只有一个指向直接后继结点的指针)
单链表中每个结点的结构:
a.单链表的操作:
1.添加:上图可以看出 单向链表只有一个指向,原来head为p, p指向s, 添加结点只需要把p指向q, q指向s就可以了,即:p--->q; q--->s;这样就实现了单向链表的参加;
2.删除:原理与添加相反,若此时链表为p--->q--->s;若删除q结点只需要更改p的指向就可以了 p--->s,这样就删掉了;
3.查找:查找操作需要对整个单链表进行遍历,直到满足查找条件为止;
4.修改:此操作一般建立在查找之上进行,找到结点之后对值进行修改;
b.头指针和头结点:
单链表有带头结点结构和不带头结点结构两种。
"链表中第一个结点的存储位置叫做头指针",如果链表有头结点,那么头指针就是指向头结点的指针。
头指针所指的不存放数据元素的第一个结点称作头结点(头结点指向首元结点)。头结点的数据域一般不放数据(当然有些情况下也可存放链表的长度,用做监视哨等)
存放第一个数据元素的结点称作第一个数据元素结点,或称首元结点。
如下图所示:
c.不带头结点的单链表如下:
d.带头结点的单链表如下:
e.不带头结点的单链表的插入操作:
上图中,是不带头结点的单链表的插入操作。如果我们在非第一个结点前进行插入操作,只需要a(i-1)的指针域指向s,然后将s的指针域指向a(i)就行了;
如果我们在第一个结点前进行插入操作,头指针head就要等于新插入结点s,这和在非第一个数据元素结点前插入结点时的情况不同。另外,还有一些不同情况需要考虑。因此,算法对这两种情况就要分别设计实现方法。
f.带头结点的单链表的插入操作:(操作统一,推荐)
上图中,如果采用带头结点的单链表结构,算法实现时,p指向头结点,改变的是p指针的next指针的值(改变头结点的指针域),而头指针head的值不变。因此,算法实现方法比较简单,其操作与对其他结点的操作统一。
问题1:头结点的好处:
头结点即在链表的首元结点之前附设的一个结点,该结点的数据域中不存储线性表的数据元素,其作用是为了对链表进行操作时,可以对空表,非空表的情况以及对首元结点进行统一处理,编程更方便。
问题2:头结点的数据域内装的是什么?
头结点的数据域可以为空,也可存放线性表长度等附加信息,但此结点不能计入链表长度值。
问题3:如何表示空表:
有头结点时,当头结点的指针域为空时表示空表。
二、单项链表的代码实现:
1.结点类:
单链表是由一个一个结点组成的,因此,要设计单链表类,必须先设计结点类。结点类的成员变量有两个:一个是数据元素,另一个是表示下一个结点的对象引用(即指针)。
步骤如下:
(1)头结点的构造(设置指针域即可)
(2)非头结点的构造
(3)获得当前结点的指针域
(4)获得当前结点数据域的值
(5)设置当前结点的指针域
(6)设置当前结点数据域的值
注:类似于get和set方法,成员变量是数据域和指针域。
2.代码实现:
(1)List.java:(链表本身也是线性表,只不过物理存储上不连续)
//线性表接口
public interface List {
//获得线性表长度
public int size();
//判断线性表是否为空
public boolean isEmpty();
//插入元素
public void insert(int index, Object obj) throws Exception;
//删除元素
public void delete(int index) throws Exception;
//获取指定位置的元素
public Object get(int index) throws Exception;
}
(2)Node.java:结点类
//结点类
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 Node getNext() {
return this.next;
}
//获得当前结点数据域的值
public Object getElement() {
return this.element;
}
//设置当前结点的指针域
public void setNext(Node nextval) {
this.next = nextval;
}
//设置当前结点数据域的值
public void setElement(Object obj) {
this.element = obj;
}
public String toString() {
return this.element.toString();
}
}
2.单链表类:
单链表类的成员变量至少要有两个:一个是头指针,另一个是单链表中的数据元素个数。但是,如果在增加一个表示单链表当前结点位置的成员变量,则有些成员函数的设计将更加方便。
代码实现:
(3)LinkList.java:单项链表类(核心代码)
//单向链表类
public class LinkList implements List {
Node head; //头指针
Node current;//当前结点对象
int size;//结点个数
//初始化一个空链表
public LinkList()
{
//初始化头结点,让头指针指向头结点。并且让当前结点对象等于头结点。
this.head = current = new Node(null);
this.size =0;//单向链表,初始长度为零。
}
//定位函数,实现当前操作对象的前一个结点,也就是让当前结点对象定位到要操作结点的前一个结点。
//比如我们要在a2这个节点之前进行插入操作,那就先要把当前节点对象定位到a1这个节点,然后修改a1节点的指针域
public void index(int index) throws Exception
{
if(index <-1 || index > size -1)
{
throw new Exception("参数错误!");
}
//说明在头结点之后操作。
if(index==-1) //因为第一个数据元素结点的下标是0,那么头结点的下标自然就是-1了。
return;
current = head.next;
int j=0;//循环变量
while(current != null&&j<index)
{
current = current.next;
j++;
}
}
@Override
public void delete(int index) throws Exception {
// TODO Auto-generated method stub
//判断链表是否为空
if(isEmpty())
{
throw new Exception("链表为空,无法删除!");
}
if(index <0 ||index >size)
{
throw new Exception("参数错误!");
}
index(index-1);//定位到要操作结点的前一个结点对象。
current.setNext(current.next.next);
size--;
}
@Override
public Object get(int index) throws Exception {
// TODO Auto-generated method stub
if(index <-1 || index >size-1)
{
throw new Exception("参数非法!");
}
index(index);
return current.getElement();
}
@Override
public void insert(int index, Object obj) throws Exception {
// TODO Auto-generated method stub
if(index <0 ||index >size)
{
throw new Exception("参数错误!");
}
index(index-1);//定位到要操作结点的前一个结点对象。
current.setNext(new Node(obj,current.next));
size++;
}
@Override
public boolean isEmpty() {
// TODO Auto-generated method stub
return size==0;
}
@Override
public int size() {
// TODO Auto-generated method stub
return this.size;
}
}
3.测试类:(单链表的应用)
使用单链表建立一个,依次输入十个0-99之间的随机数,删除第5个元素,打印输出该线性表。
(4)Test.java:
public class Test {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
LinkList list = new LinkList();
for (int i = 0; i < 10; i++) {
int temp = ((int) (Math.random() * 100)) % 100;
list.insert(i, temp);
System.out.print(temp + " ");
}
list.delete(4);
System.out.println("\n------删除第五个元素之后-------");
for (int i = 0; i < list.size; i++) {
System.out.print(list.get(i) + " ");
}
}
}
三、双链表
链式结构的另一种实现就是双向链表。双向链表维护两个引用:一个指向链表的第一个结点,另一个指向最后一个结点。链表中的每个结点存储两个引用:一个指向下一个结点,另一个指向前一个结点。
public class LinkList {
Node head;
Node tail;
int count;
public LinkList(Node head,Node tail,int count){
this.head=null;
this.tail=null;
this.count=0;
}
public LinkList(){
}
//尾插法添加节点
public void addHeadNode(NodeData data){
Node node = new Node(data,null,null);
if(head==null&&tail==null){
head=node;
head.setFront(null);
tail=node;
tail.setNext(null);
}else{
head.setFront(node);
node.setNext(head);
head=node;
head.setFront(null);
}
count++;
}
//头插法添加节点
public void addTailNode(NodeData data){
Node node = new Node(data,null,null);
if(head==null&&tail==null){
head=node;
head.setFront(null);
tail=node;
tail.setNext(null);
}else{
tail.setNext(node);
node.setFront(tail);
tail=node;
tail.setNext(null);
}
count++;
}
//查找节点
public Node findNode(NodeData data){
Node temp=head;
if(head!=null){
while(temp!=null){
if(temp.data.compare(data)){
return temp;
}
temp=temp.getNext();
}
}
return null;
}
//删除节点
public void delNode(NodeData data){
Node temp=findNode(data);
if(temp!=null){
if(temp.getFront()==null){
head=temp.getNext();
head.setFront(null);
}else if(temp.getNext()==null){
tail=tail.getFront();
tail.setNext(null);
}else{
temp.getFront().setNext(temp.getNext());
temp.getNext().setFront(temp.getFront());
}
count--;
}
}
//修改更新
public void updNode(NodeData data){
Node temp=findNode(data);
if(temp!=null){
temp.setNodeData(data);
}
}
//打印链表
public void printNode(){
Node temp = head;
while(temp!=null){
temp.print();
temp=temp.getNext();
}
}
}