彻底掌握数据结构之链表&算法(Java)
链表是由是由许多相同数据类型的数据项,按特定顺序排列而成的线性表。但链表的特性是其各个数据项在计算机内存中的位置是不连续且随机存放的,其优点是数据的插入或删除都相当方便,有新数据加入就向系统申请一块内存空间,而数据被删除后,就可以把这块内存空间还给系统,加入和删除都不需要大量移动数据。其缺点就是设计数据结构时较为麻烦,另外在查找数据时,也无法像静态数据(如数组)那样可随机读取数据,必须按序查找到该数据为止。
链表特点-动态分配内存
链表和数组的最大不同点,就是它的各个元素或者数据项的存储不必是在连续的内存中(即不必分配连续存储空间给它们),只要考虑它们在逻辑上的顺序即可。虽然数组结构也可以用来仿真链表的结构,但在进行增删或者移动元素时相当不便,而且事先必须声明固定的数组空间。
“动态分配内存”的基本精神就是:让内存的使用更具有弹性,即可在程序执行期间,根据用户的设置与需求,适当给变量分配所需要的内存空间。
1.单向链表
(1)单向链表创建
一个单向链表节点基本上是由两个元素,即数据字段和指针所组成,而指针将会指向下一个元素在内存中的地址。
在单向链表中第一个节点是“链表头指针”所在节点,最后一个节点指针指向null,表示它是“链表尾”。
由于单向链表中所有节点都知道节点本身的下一个节点在哪里,但是对于前一个节点却没有办法知道,所以在单向链表的各种操作中,“链表头指针就显得相当重要”,只要存在链表头指针就可以遍历整个链表,进行加入和删除节点等操作。除非必要,不可移动链表头指针。
在其他程序设计语言,如c或c++中,是以指针类型来处理链表类型的数据结构。由于在Java语言中没有指针类型,因此可以把链表声明为类。在其他语言中,当分配的内存不再使用时就必须释放内存空间。不过,由于Java有内存管理的垃圾回收机制,所以不存在内存垃圾不能及时收集的问题。
在Java中用Node类来模拟节点
package 链表;
public class Node {
int data;
Node next;
public Node(int data) {//节点声明构造函数
this.data=data;
this.next= null;
}
}
接着可以声明LinkedList类,这个类定义两个Node类型的节点指针,分别指向链表的第一个节点后最后一个节点。
package 链表;
public class LinkedList {
private Node first;
private Node last;
//定义类的方法
//....
}
如果链表中的节点不止记录单一数值,例如每一个节点除了指向下一个节点的指针字段外,还包括学生的姓名(name)、学号(no)、成绩(score),就可以修改Node类如下。
package 链表;
public class Node {
String name;
int no;
int score;
Node next;
public Node(String name,int no,int data) {//节点声明构造函数
this.name=name;
this.no=no;
this.score=score;
this.next=null;
}
}
建立单向链表
建立单向链表顺序:
(1)建立第一个节点
(2)将链表的first和last指针字段指向第一个节点(命名为newNode)
(3)建立另一个新节点(也命名为newNode,覆盖原来的newNode)
(4)将两个节点串起来(last指针指向newNode,一直向后移动,但是first指向第一个节点)
last.next=newNode;
last=new Node;
说明了链表创建顺序,可能同学们还是感觉比较抽象,接下来我们通过一个实例来演示单向链表创建过程。
【实例单向链表创建】
请设计一个Java程序,可以让用户输入数据来添加学生数据节点,以建立一个单向链表。一共输入5位学生的成绩来建立好单向链表,然后遍历这个单向链表的每一个节点来打印输出学生的成绩。单向链表的遍历(Traverse)就是访问链表中的每个节点。
解:
首先创建LinkList程序。在此程序中声明了Node类和LinkedList类。在LinkedList类中,除了定义两个Node类节点指针,分别指向链表的第一个节点和最后一个节点外,还在该类中声明了三个方法。
package 单向链表创建;
/**
* 方法功能解释:
* public boolean isEmpty()判断当前链表是否为空链表
* public void print()打印链表内容
*public insert(int data,String names,int np)将指定节点插入到当前链表
*/
class Node{
int data;
String names;
int np;
Node next;
public Node(int data,String names,int np) {
this.data=data;
this.names=names;
this.np=np;
this.next=null;
}
}
public class LinkedList {
private Node first;
private Node last;
public boolean isEmpty() {
return first==null;
}
public void print() {
Node current=first;
while(current!=null) {
System.out.println("["+current.data+" "+current.names+" "+current.np+"]");
current=current.next;
}
}
public void insert(int data,String names,int np) {
Node newNode=new Node(data,names,np);
if(this.isEmpty()) {
first=newNode;
last=newNode;
}else {
last.next=newNode;
last=newNode;
}
}
}
接着一共输入5位学生的成绩来建立好单向链表,然后遍历这个单向链表的每一个节点来打印输出学生的成绩。
package 单向链表创建;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class 学生成绩 {
public static void main(String[] args) throws IOException {
BufferedReader buf;
buf=new BufferedReader(new InputStreamReader(System.in));
int num;
String name;
int score;
System.out.println("请输入5位学生的数据:");
LinkedList list=new LinkedList();
for(int i=1;i<6;i++) {
System.out.println("请输入学号:");
num=Integer.parseInt(buf.readLine());
System.out.println("请输入姓名:");
name=buf.readLine();
System.out.println("请输入成绩:");
score=Integer.parseInt(buf.readLine());
list.insert(num, name, score);
System.out.println("--------------------");
}
System.out.println("学生成绩");
System.out.println("学生姓名成绩=====================");
list.print();
}
}
注意:这两个程序写在一个包中。
运行结果如下:
(2)单向链表节点删除
单向链表节点删除分为三种型式:
①删除头节点,只需要将头指针向后移动一个位置
if(delNode.data==first.data){
first=first.next;
}
②删除中间节点,只需要将删除节点指针指向对应的下一个节点就可以
newNode=first;
tmp=first;
if(newNode.data!=delNode.data){
tmp=newNode;
newNode=newNode.next
}
tmp.next=delNode.next;
③删除尾节点,只需要将尾节点头一个节点指向null
if(last.data==delNode.data){
newNode=first;
while(newNode.next!=last)newNode=newNode.next;
newNode.next=last.next;
last=newNode;
}
【范例单向链表节点删除】
请设计一个Java程序,来实现建立一组学生成绩的单向链表,包含了学号、姓名与成绩三种数据。只要输入想要删除的成绩,就可以遍历此列表,并清除该位学生的节点。要结束输入时,请输入“-1”,则此时会列出此列表未删除的所有学生数据。
首先写StuLinkedList程序来创建链表并且定义插入和删除方法
package 链表节点删除;
class Node{
int data;//学号
String names;//姓名
int np;//成绩
Node next;
public Node(int data,String names,int np) {
this.data=data;
this.names=names;
this.np=np;
this.next=null;
}
}
public class StuLinkedList {
public Node first;
public Node last;
public boolean isEmpty() {
return first==null;
}
public void print() {
Node current=first;
while(current!=null){
System.out.println("["+current.data+" "+current.names+" "+current.np+"]");
current=current.next;
}
System.out.println();
}
public void insert(int data,String names,int np) {//从末尾插入数据
Node newNode=new Node(data,names,np);
if(this.isEmpty()) {
first=newNode;
last=newNode;
}
else {
last.next=newNode;
last=newNode;
}
}
public void delete(Node delNode) {
Node newNode;
Node tmp;
if(first.data==delNode.data) {//如果删除节点是头节点将头节点指针指向下一个节点
first=first.next;
}else if(last.data==delNode.data) {//如果删除节点是尾节点就将倒数第二个节点指向尾节点的指针指向尾节点的下一个节点
System.out.println("I am here\n");
newNode=first;
while(newNode.next!=last)newNode=newNode.next;
newNode.next=last.next;
last=newNode;
}else {//如果删除节点在中间,找到删除节点的前一个节点,前一个节点指向下一个节点的指针指向删除节点的下一个节点
newNode=first;
tmp=first;
while(newNode!=delNode) {
tmp=newNode;
newNode=newNode.next;
}
tmp.next=newNode.next;
}
}
}
然后写Student类来给链表填入数据,并且实现删除操作。
package 链表节点删除;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Random;
//使用链表来建立打印学生成绩
public class Student {
public static void main(String[] args) throws NumberFormatException, IOException {
BufferedReader buf;
Random rand=new Random();
buf=new BufferedReader(new InputStreamReader(System.in));
StuLinkedList list=new StuLinkedList();
int i,j,findword=0,data[][]=new int[12][2];
String name[]=new String[] {"Allen","Scott","Marry","Jon","Mark","Ricky","Lisa","Jasica","Hanson","Amy","Bob","Jack"};
System.out.println("学号\t成绩\t学号\t成绩\t学号\t成绩\t学号\t成绩\t\n");
for(i=0;i<12;i++) {
data[i][0]=i+1;
data[i][1]=(Math.abs(rand.nextInt(50)))+50;
list.insert(data[i][0], name[i], data[i][1]);
}
for(i=0;i<3;i++) {
for(j=0;j<4;j++) {
System.out.print("["+data[j*3+i][0]+"]\t["+data[j*3+i][1]+"]\t");//按照列顺序将一维数组data[i][0]打印成三行四列的二维数组
}
System.out.println();
}
while(true) {
System.out.print("请输入要删除成绩的学生学号,结束输入-1:");
findword=Integer.parseInt(buf.readLine());
if(findword==-1) {
System.out.println("结束删除操作!");
break;
}else {
Node current=new Node(list.first.data,list.first.names,list.first.np);//通过创建current遍历链表找到要删除节点的指针位置
current.next=list.first.next;
while(current.np!=findword)current=current.next;
list.delete(current);//找到删除节点指针位置,传入给删除函数
}
System.out.println("删除成绩后的链表,请注意!要删除的成绩其学生的学号必须在此链表种。\n");
list.print();
}
}
}
结果如下:
(3)单向链表插入新节点
在单向链表种插入新节点,也有三种方法:
①新节点插入第一个节点之前,即成为链表的首节点:只需要把新节点的指针指向链表原来的第一个节点,再把链表头指针指向新节点即可。
②新节点插入最后一个节点之后:只需要把链表的最后一个节点的指针指向新节点,新节点的指针再指向null即可。
③将新节点插入链表中间的位置:例如插入的节点是在X和Y之间,只要将X节点的指针指向新节点,新节点的指针指向Y节点即可。
以下是用Java语言实现的链表插入节点的算法:
public void insert(Node ptr) {
Node tmp;
Node newNode;
if(this.isEmpty()) {
first=ptr;
last=ptr;
}else {
if(ptr.next==first)//插入到第一个节点
{
ptr.next=first;
first=ptr;
}else {
if(ptr.next==null)//插入到最后一个节点
{
last.next=ptr;
ptr=last;
}else {
newNode=first;
tmp=first;
while(newNode.next!=ptr.next) {//找到插入节点的前一个节点和后一个节点,如果本次循环ptr下一个节点等于newNode的下一个节点,那么这次的newNode是ptr的前一个节点,因为本次循环不执行,即这次判断的newNode就是上次循环的tmp,即tmp是ptr的前一个节点,newNode是ptr的下一个节点
tmp=newNode;
newNode=newNode.next;
}
tmp.next=ptr;//前一个节点tmp指针指向ptr
ptr.next=newNode;//ptr指针指向下一个节点newNode
}
}
}
}
【范例插入新节点到链表】
请设计一个Java程序,来实现单向链表添加节点的过程,并且允许可以在链表头部、链表末尾和链表中间三种不同位置插入新节点。
解:这次把三个类写在一个程序中。
package 单向链表插入新节点;
class Node{
int data;
Node next;
public Node(int data) {
this.data=data;
this.next=null;
}
}
class LinkedList{
public Node first;
public Node last;
public boolean isEmpty() {
return first==null;
}
public void print() {
Node current=first;
while(current!=null) {
System.out.print("["+current.data+"]");
current=current.next;
}
System.out.println();
}
//串接两个链表
public LinkedList Concatenate(LinkedList head1,LinkedList head2) {//将第一个链表的尾节点指向第二个链表的头节点
LinkedList ptr;
ptr=head1;
while(ptr.last.next!=null) {
ptr.last=ptr.last.next;
}
ptr.last.next=head2.first;
return head1;
}
//插入节点
public void insert(Node ptr) {
Node tmp;
Node newNode;
if(this.isEmpty()) {
first=ptr;
last=ptr;
}else {
if(ptr.next==first)//插入到第一个节点
{
ptr.next=first;
first=ptr;
}else {
if(ptr.next==null)//插入到最后一个节点
{
last.next=ptr;
ptr=last;
}else {
newNode=first;
tmp=first;
while(newNode.next!=ptr.next) {
tmp=newNode;
newNode=newNode.next;
}
ptr=tmp.next;
ptr.next=newNode;
}
}
}
}
}
public class 插入新节点 {
public static void main(String[] args) {
LinkedList list1=new LinkedList();
LinkedList list2=new LinkedList();
Node node1=new Node(5);
Node node2=new Node(6);
list1.insert(node1);
list1.insert(node2);
Node node3=new Node(7);
Node node4=new Node(8);
list2.insert(node3);
list2.insert(node4);
list1.Concatenate(list1, list2);
list1.print();
}
}
运行结果如下:
(4)单向链表的反转
单向链表的反转和增加、删除不同,因为每一个节点只有一个指向下一个节点的指针,所以我们找不到每一个节点的前一个节点。如果我们反转单向链表,就需要再加入一个指向前一个节点的指针变量。
在这里通过一个范例程序来演示:
【范例单向链表反转】
解:先按照从前往后的顺序构建好单向链表,基本上把上一个程序的建表代码复制下来就行。我们命名为 StuLinkedList程序
package 单向链表反转;
class Node{
int data;//学号
String names;//姓名
int np;//成绩
Node next;
public Node(int data,String names,int np) {
this.data=data;
this.names=names;
this.np=np;
this.next=null;
}
}
public class StuLinkedList {
public Node first;
public Node last;
public boolean isEmpty() {
return first==null;
}
public void print() {
Node current=first;
while(current!=null){
System.out.println("["+current.data+" "+current.names+" "+current.np+"]");
current=current.next;
}
System.out.println();
}
public void insert(int data,String names,int np) {//从末尾插入数据
Node newNode=new Node(data,names,np);
if(this.isEmpty()) {
first=newNode;
last=newNode;
}
else {
last.next=newNode;
last=newNode;
}
}
public void delete(Node delNode) {
Node newNode;
Node tmp;
if(first.data==delNode.data) {//如果删除节点是头节点将头节点指针指向下一个节点
first=first.next;
}else if(last.data==delNode.data) {//如果删除节点是尾节点就将倒数第二个节点指向尾节点的指针指向尾节点的下一个节点
System.out.println("I am here\n");
newNode=first;
while(newNode.next!=last)newNode=newNode.next;
newNode.next=last.next;
last=newNode;
}else {//如果删除节点在中间,找到删除节点的前一个节点,前一个节点指向下一个节点的指针指向删除节点的下一个节点
newNode=first;
tmp=first;
while(newNode!=delNode) {
tmp=newNode;
newNode=newNode.next;
}
tmp.next=newNode.next;
}
}
}
然后,我们再写一个Student程序,在该程序里构建ReverseStuLinkedList类和Student类,其中ReverseStuLinkedList类继承StuLinkedList类。给链表赋值并完成反转。
代码如下:反转的算法在注释中详细解答
package 单向链表反转;
import java.util.Random;
//单向链表反转功能
class ReverseStuLinkedList extends StuLinkedList {
public void reverse_print() {
Node current=first;
Node before=null;
System.out.println("反转后的链表数据:");
while(current!=null) {//反转链表指针
last=before;//before赋值给last,让before的指向下一个节点的指针掉过头来指向last,实现反转
//这里last只作为一个指针变量,随着before向后移动,单向链表值只和头节点first有关,不改变头节点链表不变
before=current;//将当前节点(从第一个节点开始向后移动)赋值给before
current=current.next;//当前节点向后移动一位,再赋值给before和last,故before和last均向后移动一位
before.next=last;//before掉过头指向last
/**
* 注意在这个while循环里不能将最后两条赋值语句交换位置,否则在将current赋值给before的情况下改变before的指针,
* current的指针也会随之改变。将current.next赋值给current就不会受到之前的影响
*/
}//在这里和之前链表不同的是,last节点为空节点,之前的last.next才为空节点,性能是一样的,同学们可以改成自己喜欢的样子
current=before;//这时的before已经移动到原链表的最后一个节点位置,并且指向前面的节点,赋值给current进行遍历
while(current!=null) {//从后向前遍历链表,打印输出
System.out.println("["+current.data+" "+current.names+" "+current.np+"]");
current=current.next;
}
System.out.println();
}
}
public class Student {
public static void main(String[] args) {
Random rand=new Random();
ReverseStuLinkedList list=new ReverseStuLinkedList();
int i,j,data[][]=new int[12][10];
String name[]=new String[] {"Allen","Scott","Marry","Jon","Mark","Ricky","Lisa","Jasica","Hanson","Amy","Bob","Jack"};
System.out.println("学号\t成绩\t学号\t成绩\t学号\t成绩\t学号\t成绩\t\n");
for(i=0;i<12;i++) {
data[i][0]=i+1;
data[i][1]=(Math.abs(rand.nextInt(50)))+50;
list.insert(data[i][0], name[i], data[i][1]);
}
for(i=0;i<3;i++) {
for(j=0;j<4;j++) {
System.out.print("["+data[j*3+i][0]+"]\t["+data[j*3+i][1]+"]\t");//按照列顺序将一维数组data[i][0]打印成三行四列的二维数组
}
System.out.println();
}
list.print();
list.reverse_print();
}
}
运行结果如下:
(5)单向链表的连接
单向链表之间的连接特别方便,只需要将其中一个链表的尾节点指向另一个链表的头节点,把另一个链表的尾节点赋值给新链表的尾节点。
【链表连接算法展示】
package 单向链表的连接;
class Node{
int data;
Node next;
public Node(int data) {
this.data=data;
this.next=null;
}
}
public class Linkelist {
public Node first;
public Node last;
public boolean isEmpty() {
return first==null;
}
public void print() {
Node current=first;
while(current!=null) {
System.out.print("["+current.data+"]");
current=current.next;
}
System.out.println();
}
/*连接两个链表*/
public Linkelist Concatenate(Linkelist head1,Linkelist head2) {
Linkelist ptr;
ptr=head1;
while(ptr.last.next!=null) {
ptr.last=ptr.last.next;
}
ptr.last.next=head2.first;
return head1;
}
}
(6)多项式链表表示法
同数组中所讲的一样,n次多项式表示方式为p(x)=anx(n-1)+an-1(n-1)+…a1x+a0.数组中的两种表示方法在上一篇博客中有详细介绍。链接如下https://blog.csdn.net/weixin_50686532/article/details/117438841?spm=1001.2014.3001.5501。
这里我们再用单向链表来表示多项式,多项式链表表示法主要存储非零项,并且每一项均符合以下数据结构。
COEF:该变量的系数
EXP:该变量的指数
LINK:指向下一个节点的指针
多项式以单链表的方式表示的作用,主要是用于多项式的四则运算。对于两个多项式相加,从左往右逐一比较幂次大小,当发现指数幂次大者,则将此节点加入到新链表C(x),指数幂次相同者系数相加后不为零也存入c(x),直到两个多项式的每一项都比较完毕。接下来我们用实际编程来解决问题。
【范例单向链表之多项式相加】
package 单向链表之多项式相加;
class Node{
int coef;//系数
int exp;//指数
Node next;//指针
public Node(int coef,int exp) {
this.coef=coef;
this.exp=exp;
this.next=null;
}
}
class PolyLinkedList{
public Node first;
public Node last;
public boolean isEmpty() {
return first==null;
}
public void creat_link(int coef,int exp) {//创建链表
Node newNode=new Node(coef,exp);
if(this.isEmpty()) {
first=newNode;
last=newNode;
}else {
last.next=newNode;
last=newNode;
}
}
public void print_link() {//打印链表
Node current=first;
while(current!=null) {
if(current.exp==1&¤t.coef!=0)//X^1不显示指数
{
System.out.print(current.coef+"X+ ");
}else {
if(current.exp!=0&¤t.coef!=0)
{
System.out.print(current.coef+"X^"+current.exp+"+ ");
}
else {
if(current.coef!=0)//X^0不显示变量
{
System.out.print(current.coef);
}
}
}
current=current.next;
}
System.out.println();
}
public PolyLinkedList sum_link(PolyLinkedList b) {//多项式链表相加,返回一个链表对象
int sum[]=new int[10];//存储系数和
int[] tempexp=new int[10];//存储指数
//这里可以给存储系数和指数的数组更大空间,虽然我只给了10个
int i=0,maxnumber;//变量i和最大次数
PolyLinkedList tempLinkedList=new PolyLinkedList();//存储结果的链表
PolyLinkedList a=new PolyLinkedList();
a=this;//用a表示本链表
while(a.first!=null) {
if(a.first.exp==b.first.exp) {
sum[i]=a.first.coef+b.first.coef;
tempexp[i]=a.first.exp;
a.first=a.first.next;
b.first=b.first.next;//虽然改变了a和b的头节点,但是不影响结果链表
i++;
}else if(b.first.exp>a.first.exp) {
sum[i]=b.first.coef;
tempexp[i]=b.first.exp;
b.first=b.first.next;
i++;
}else if(a.first.exp>b.first.exp) {
sum[i]=a.first.coef;
tempexp[i]=a.first.exp;
a.first=a.first.next;
i++;
}
}
maxnumber=i-1;//最大次数
for(int j=0;j<maxnumber+1;j++) {
tempLinkedList.creat_link(sum[j], maxnumber-j);
}
return tempLinkedList;
}
}
public class 多项式相加 {
public static void main(String[] args) {
PolyLinkedList a=new PolyLinkedList();
PolyLinkedList b=new PolyLinkedList();
PolyLinkedList c=new PolyLinkedList();
int data1[]= {8,54,7,0,1,3,0,4,2};//A的系数
int data2[]= {-2,6,0,0,0,5,6,8,6,9};//多项式B的系数
for(int i=0;i<data1.length;i++) {
a.creat_link(data1[i],data1.length-i-1);
}
for(int i=0;i<data2.length;i++) {
b.creat_link(data2[i],data2.length-i-1);
}
System.out.print("原始多项式为:\nA=");
a.print_link();
System.out.print("\nB=");
b.print_link();
System.out.print("多项式相加结果为:\nC=");
c=a.sum_link(b);
c.print_link();
}
}
运行结果如下:
2.环形链表
在单向链表中,链表的头节点不可改变,如果改变头节点指针后找不到其他节点,整个链表都会遗失,并且浪费了整个链表的内存空间。
如果我们把链表的尾节点指针指向头节点,那么整个链表就成为一个单方向的环形结构。这样就不用担心链表指针遗失的问题了,因为链表的每一个节点就可当头节点用来遍历整个链表。环形链表通常应用于内存工作区和输入输出缓冲区。
环形链表和单向链表的不同:
①创建方式:环形链表创建时最后一个节点指针需要指向头节点
②遍历时间:环形链表每一个节点遍历链表的时间都是一样的
③尾部或头部插入节点:环形链表尾部和头部插入都需要改变两个节点指针。
(1)环形链表新节点的插入
插入步骤(头部插入):
①将新节点的指针指向原链表头
②找到原链表的最后一个节点,并指向新节点。
③将链表头指向新节点
中间插入和单向链表一样,尾部插入和头部插入类似。
(2)环形链表中节点的删除
删除步骤(删除头节点):
①将链表头head移到下一个节点
②将最后一个节点的指针移到新的链表头
(删除中间节点):
①找到被删除节点的前一个节点
②将它指向被删除节点的下一个节点
【环形链表插入和删除算法】
package 环形链表插入和删除;
class Node{
int data;
Node next;
public Node(int data) {
this.data=data;
this.next=null;
}
}
public class CircleLink {
public Node first;
public Node last;
public boolean isEmpty() {
return first==null;
}
public void print() {
Node current=first;
while(current!=last) {
System.out.print("["+current.data+"]");
current=current.next;
}
System.out.print("["+current.data+"]");
System.out.println();
}
/*插入新节点*/
public void insert(Node trp) {
Node tmp;
Node newNode;
if(isEmpty()) {
first=trp;
last=trp;
last.next=first;
}else if(trp.next==null){//从尾部插入
last.next=trp;
last=trp;
last.next=first;
}else {//从某一个节点之前插入
newNode=first;
tmp=first;
while(newNode.next!=trp.next) {
if(tmp.next==first) {//如果遍历到尾节点仍然没有找到便退出循环
break;
}
tmp=newNode;
newNode=newNode.next;
}
tmp.next=trp;
trp.next=newNode;
}
}
/*删除节点*/
public void delete(Node delNode) {
Node newNode;
Node tmp;
if(this.isEmpty()) {
System.out.print("[环形链表已经空了]");
return;
}
if(first.data==delNode.data) {//要删除的节点是链表头部
first=first.next;
if(first==null) {
System.out.print("[环形链表已经空了]");
}
return;
}else if(last.data==delNode.data) {//要删除的节点是链表尾部
newNode=first;
while(newNode.next!=last){
newNode=newNode.next;
}
newNode.next=last.next;
last=newNode;
last.next=first;
}else {//要删除的节点是链表中间节点
newNode=first;
tmp=first;
while(newNode.data!=delNode.data) {
tmp=newNode;
newNode=newNode.next;
}
tmp.next=delNode.next;
}
}
}
(3)环形链表的串连
单向链表的串联只要将一个链表的尾节点指向另一个链表的头节点,环形链表的串联稍微复杂一点。因为环形链表不分头尾,所以不需要遍历链表去查找链表尾部。只需要分别改变两个链表中的一个指针,使其中链表1的一个节点指针指向链表2的一个节点,链表2被指向节点的前一个节点指向链表1断开的那个节点。就可以把两个环形链表串联在一起了。
【范例环形链表串联】
首先写一个环形链表的串建程序,StuLinkedList
package 环形链表的串联;
class Node{
int data;
String names;
int np;
Node next;
public Node(int data,String names,int np) {
this.data=data;
this.names=names;
this.np=np;
this.next=null;
}
}
public class StuLinkedList {
public Node first;
public Node last;
public boolean isEmpty() {
return first==null;
}
public void print() {
Node current=first;
int i=0;
while(current!=last) {
System.out.print("["+current.data+"\t"+current.names+"\t"+current.np+"]"+"\t");
current=current.next;
i++;//换行
if(i%5==0) {
System.out.println();
}
}
System.out.print("["+current.data+"\t"+current.names+"\t"+current.np+"]");
System.out.println();
}
/*插入新节点*/
public void insert(int data,String names,int np) {
Node trp=new Node( data, names, np);
Node tmp;
Node newNode;
if(isEmpty()) {
first=trp;
last=trp;
last.next=first;
}else if(trp.next==null){//从尾部插入
last.next=trp;
last=trp;
last.next=first;
}else {//从某一个节点之前插入
newNode=first;
tmp=first;
while(newNode.next!=trp.next) {
if(tmp.next==first) {//如果遍历到尾节点仍然没有找到便退出循环
break;
}
tmp=newNode;
newNode=newNode.next;
}
tmp.next=trp;
trp.next=newNode;
}
}
/*删除节点*/
public void delete(Node delNode) {
Node newNode;
Node tmp;
if(this.isEmpty()) {
System.out.print("[环形链表已经空了]");
return;
}
if(first.data==delNode.data) {//要删除的节点是链表头部
first=first.next;
if(first==null) {
System.out.print("[环形链表已经空了]");
}
return;
}else if(last.data==delNode.data) {//要删除的节点是链表尾部
newNode=first;
while(newNode.next!=last){
newNode=newNode.next;
}
newNode.next=last.next;
last=newNode;
last.next=first;
}else {//要删除的节点是链表中间节点
newNode=first;
tmp=first;
while(newNode.data!=delNode.data) {
tmp=newNode;
newNode=newNode.next;
}
tmp.next=delNode.next;
}
}
}
然后连接环形链表,并且赋值输出,在这里我直接在单向链表连接赋值程序上做了修改:
package 环形链表的串联;
import java.util.Random;
//将两个学生表串联起来
class ConcatStuLinkedList extends StuLinkedList{
public StuLinkedList concat(StuLinkedList stulist) {
this.last.next=stulist.first;//将链表1的尾节点指向链表2的头节点
stulist.last.next=this.first;//将链表2的尾节点指向链表1的头节点
this.last=stulist.last;//将链表2的尾节点代替给链表1的尾节点
return this;
}
}
public class Student {
public static void main(String[] args) {
Random rand=new Random();
ConcatStuLinkedList list1=new ConcatStuLinkedList();
ConcatStuLinkedList list2=new ConcatStuLinkedList();
int i,j,data[][]=new int[12][10];
String name1[]=new String[] {"Allen","Scott","Marry","Jon","Mark","Ricky","Lisa","Jasica"};
String name2[]=new String[] {"Hanson","Amy","Bob","Jack","xiaomin","xiaohong","xiaobai","xiaohei"};
// System.out.println("学号\t成绩\t学号\t成绩\t学号\t成绩\t学号\t成绩\t\n");
for(i=0;i<8;i++) {
data[i][0]=i+1;
data[i][1]=(Math.abs(rand.nextInt(50)))+50;
list1.insert(data[i][0], name1[i], data[i][1]);
}
// for(i=0;i<2;i++) {
// for(j=0;j<4;j++) {
// System.out.print("["+data[j+i*4][0]+"]\t["+data[j+i*4][1]+"]\t");//按照列顺序将一维数组data[i][0]打印成三行四列的二维数组
// }
// System.out.println();
// }
for(i=0;i<8;i++) {
data[i][0]=i+9;
data[i][1]=(Math.abs(rand.nextInt(50)))+50;
list2.insert(data[i][0], name2[i], data[i][1]);
}
// for(i=0;i<2;i++) {
// for(j=0;j<4;j++) {
// System.out.print("["+data[j+i*4][0]+"]\t["+data[j+i*4][1]+"]\t");//按照列顺序将一维数组data[i][0]打印成三行四列的二维数组
// }
// System.out.println();
// }
list1.concat(list2);
list1.print();
}
}
程序运行结果如下:
(4)稀疏矩阵环形链表表示法
数组表示稀疏矩阵虽然比较节省时间,但是当非零项要增删时会造成数组内大量的数据移动,而且程序编写也不容易。
环形链表表示稀疏矩阵,它最大的优点是:在变更矩阵内的数据时,不需要大量移动数据,只需要改变节点间的指针。由于矩阵时二维的,因此每个节点除了必须有3个数据字段:Row(行),Col(列)和Value(数据)外,还必须有两个指针:Right、Down,其中right指针可用来连接同一行的节点,down指针可以用来连接同一列的节点。
3.双向链表
单向链表和环形链表都是属于拥有方向性的链表,只能单向遍历,万一其中有一个链接断裂,那么后面的数据都无法复原了。我们现在可以用更高级的双向链表来解决这个问题,将两个方向不同的链表结合起来,除了存放数据的字段外,它有两个指针变量,其中一个指针指向后面的节点,另一个则指向前面的节点。
双向链表的每一个节点都可以双向通行,因此能够轻松找到前后节点,同时从链表的任意节点也可以找到其他节点。不需要经过反转或对比节点等处理,执行速度较快。另外,如果任意节点的链接断裂,可以通过反向链表进行遍历,可以快速的重建完整的链表。
双向链表最大的优点是:一个节点有两个指针分别指向节点前后两个节点,所以能轻松找到前后节点,同时从双向链表中任一节点也可以找到其他节点并且因为不需要经过反转或对比节点等处理,执行速度很快。
缺点是:因为每一个节点有两个指针,所以在加入或删除节点时都得花更多的时间来调整指针,另外因为每一个节点有两个指针变量,较浪费存储空间。
(1)双向链表的定义
双向链表节点的构成如下:
(1)每个节点都有三个字段,中间为数据字段。左右各有两个链接字段,分别为LLink和RLink。其中LLink指向前一个节点,RLink指向下一个节点。
(2)双向链表在头节点之前通常放一个的链表头,链表头不存放任何数据,其左指针指向尾节点,右指针指向头节点。
(3)假设ptr为此链表上的任意一个节点,则有:
ptr=ptr.LLink.RLink=ptr.RLink.LLink;
使用Java声明双向链表节点,代码如下:
class Node{
int data;
Node lnext;
Node rnext;
public Node(int data){
this.data=data;
this.lnext=null;
this.rnext=null;
}
}
(2)双向链表节点的插入
双向链表节点的插入有三种情况:
第一种,将新节点插入到双向链表的第一个节点前,
步骤一:将新节点的右链接(RLink)指向原链表的第一个节点
步骤二:将原链表的第一个节点的左链接(LLink)指向新节点
步骤三:将原链表的表头指针head指向新节点,且新节点的左链接指向null
第二种,将新节点插入到双向链表的末尾
步骤一:将原链表的最后一个节点的右链接指向新节点
步骤二:将新节点的左链接指向原链表的最后一个节点,并将新节点的右链接指向null
第三种,将新节点插入到双向链表中间的任一位置(ptr指向的节点)之后
步骤一:将ptr(前一个节点)节点的右链接指向新节点
步骤二:将新节点的左链接指向ptr节点
步骤三:将ptr节点的下一个节点的左链接指向新节点
步骤四:将新节点的右链接指向ptr的下一个节点
(3)双向链表节点的删除
对于双向链表节点删除,同样有三种情况:
第一种,删除双向链表的第一个节点
步骤一:将链表头指针head指向原链表的第二个节点
步骤二:将新的链表头指针指向null
第二种,删除此链表的最后一个节点
步骤一:将原链表最后一个节点之前一个节点的右链接指向null
第三种,删除ptr(指针)指向的链表中间的节点
步骤一:将ptr节点的前一个节点右链接指向ptr节点的下一个节点
步骤二:将ptr节点的下一个节点左链接指向ptr节点的上一个节点
双向链表声明的数据结构、建立节点、插入节点以及删除节点的算法如下
package 双向链表;
class Node{
int data;
Node rnext;
Node lnext;
public Node(int data) {
this.data=data;
this.rnext=null;
this.lnext=null;
}
}
public class Doublely {
public Node first;
public Node last;
public boolean isEmpty() {
return first==null;
}
public void print() {
Node current=first;
while(current!=null) {
System.out.print("["+current.data+"]");
current=current.rnext;
}
System.out.println();
}
//插入节点
public void insert(Node newN) {
Node tmp;
Node newNode;
if(this.isEmpty()) {
first=newN;
first.rnext=last;
last=newN;
last.lnext=first;
}else {
if(newN.lnext==null) {//插入到链表头部的位置
first.lnext=newN;
newN.rnext=first;
first=newN;
}else if(newN.rnext==null){
//插入到链表尾部的位置
last.rnext=newN;
newN.lnext=last;
last=newN;
}else {
//插入到链表中间节点的位置
newNode=first;
tmp=first;
while(newN.rnext!=newNode.rnext) {
tmp=newNode;
newNode=newNode.rnext;
}
tmp.rnext=newN;
newN.lnext=tmp;
newN.rnext=newNode;
newNode.lnext=newN;
}
}
}
//删除节点
public void delete(Node delNode) {
Node newNode;
Node tmp;
if(first==null) {
System.out.print("[链表是空的]\n");
return;
}
if(delNode==null) {
System.out.print("[错误:del不是链表种的节点]\n");
return;
}
if(first.data==delNode.data) {//要删除的节点在链表头部
first=first.rnext;
first.lnext=null;
}
else if(last.data==delNode.data) {//要删除的节点在链表尾部
newNode=first;
while(newNode.rnext!=last) {
newNode=newNode.rnext;
}
newNode.rnext=null;
last=newNode;
}else {
newNode=first;
tmp=first;
while(newNode.data!=delNode.data) {
tmp=newNode;
newNode=newNode.rnext;
}
tmp.rnext=delNode.rnext;
delNode.rnext.lnext=tmp;
}
}
}