1.什么是链表?
2.链表有哪些特点?
3.为什么要使用链表?
4.链表分为哪几种形式?
直观来说,项链,佛珠,手表都是链表的一种表现形式,链表就是一种由一系列结点构成的数据结构。
链表的特点:链表是由一系列结点构成,每个结点至少包含两个域(数据域和指向下个结点的引用类型),双向单链表和循环单链表还有个指向父节点的引用类型域。链表中的结点存放的物理地址是非连续的,链表是不定长的,它可以随时加入新的结点。
之前我们用数组实现过队列的方法,既然用数组可以实现,那为什么还要用链表去实现队列的方法呢?数组和链表到底有什么区别?
数组是定长的,数组有下标,这样使得数组查找比较方便,直接通过下标就可得到想要查找的元素,当用数组去实现查找元素操作时,时间复杂度为1,数组中的元素存放的物理地址是连续的,这样就使得数组实现插入和删除的操作比较麻烦,每插入或删除一个元素平均要移动数组中一半的数据元素。然而,链表正好与数组的功能相反,数组的优点正好是链表的缺点,反之亦然。所以,可以根据对数据的不同操作,选择使用这两种方法。当要实现插入或删除操作时,就选择链表,因为链表只需修改指向下个结点引用类型的值,时间复杂度为1.
链表分为线性链表和非线性链表。
线性链表主要包括:单链表,双向链表,循环链表。
非线性链表主要包括:树,图等等。
对单链表的操作:
用链表实现队列的方法:
加入操作:java代码
public void add(Object obj){
if(root==null){//如果队列中还没有结点
root=new LinkNode(obj);//将要添加的作为头结点
end=root;//同时也将尾结点指向头结点
}
else{
LinkNode node=new LinkNode(obj);//先要创建一个结点
end.setNext(node);//将链表串接起来
end=node;//然后在使尾结点移动到该结点上
}
}
求队列的长度:
public int size(){
int count=0;
if(root==null){//如果根结点为空
return 0;//返回0
}
else{//如果根结点不为空,
count++;//计数器+1
LinkNode node=root.getNext();//得到根结点的下一个结点
while(node!=null){
count++;//计数器+1
node =node.getNext();
}
return count;
}
}
根据下标得到一个元素:
public LinkNode get(int index){
int count=0;
if(index>=size()||index<0){//如果给定的值不在范围之内
throw new RuntimeException("你输入已超出范围 index="+index);//抛出异常
}
else{
if(index==0){//如果索引为0
return root;//返回根结点的值
}
else{
LinkNode node=root.getNext();//得到根结点的下个结点
while(node!=null){//如果该结点不为空
count++;//使计数器+1
if(index==count){
return node;
}
else
node=node.getNext();
}
}
}
return null;
}
往队列中插入一个结点:
public void insert(LinkNode node,int index){
if(index<0){//如果插入的位置不准确
throw new RuntimeException("你输入已超出范围 index="+index);//抛出异常
}
else if(index==0){//如果插入的位置为0
node.setNext(root);//将该结点指向根结点就可以了
root=node;//然后在使根结点移动到该结点的位置上
}
else if(index>0&&index<=size()-1){//如果插入的位置既不是队列的头部,也不是队列的末尾
LinkNode node1=get(index-1);//首先要得到插入位置的前一个结点
LinkNode node2=get(index);//还要得到在插入以前该位置的结点
node1.setNext(node);//然后将它们串接起来
node.setNext(node2);
}
else{//如果插入的位置是在末尾
end.setNext(node);//直接将结点插入到队列的末尾
end=node;//将尾结点指向该结点
}
}
将对列中的某个结点删除:
public void remove(int index){
if(index<0||index>=size()){//如果插入的位置不准确
throw new RuntimeException("你输入已超出范围 index="+index);//抛出异常
}
else if(index==0){//如果删除的位置为0
LinkNode node1=root.getNext();//得到根结点的下个结点
root=node1;//将根结点指向该结点
}
else if(index>0&&index<size()-1){//如果删除的位置既不是队列的头部,也不是队列的末尾
LinkNode node1=get(index-1);//首先要得到删除位置的前一个结点
LinkNode node2=get(index);//得到要删除的这个结点
node1.setNext(node2.getNext());//删除位置的前一个结点指向要删除的这个结点的下个结点
}
else{//如果删除的是最后的位置
LinkNode node1=get(index-1);//得到它前面一个位置
node1.setNext(null);
}
}
修改结点的值:
public void xiuGai(LinkNode node,int index){
if(index<0&&index>size()-1){
throw new RuntimeException("你输入已超出范围 index="+index);//抛出异常
}
else {
LinkNode node1=get(index);
node1.setObj(node.getObj());
}
}
双向链表的理解:
双向链表其实跟单链表结构类似,只是比单链表多了个指向父节点的引用类型域而已,所以在建双向链表时,只需将除根结点外的其他个结点的指向父节点的引用类型域指向它的前一个结点即可。
双向链表的操作:
建立一个双向链表:
public static LinkNode creatDoubleLink(String [] a){
LinkNode root=new LinkNode(a[0]);
LinkNode node1=new LinkNode(a[1]);
LinkNode node2=new LinkNode(a[2]);
LinkNode node3=new LinkNode(a[3]);
root.setNext(node1);
node1.setNext(node2);
node1.setParent(root);
node2.setParent(node1);
node2.setNext(node3);
node3.setParent(node2);
return root;
}
遍历双向链表:
public static void printNode(LinkNode root){
if(root.getNext()!=null){
Object obj=root.getObj();
System.out.println("双向链表的数据元素"+obj);
LinkNode node=root.getNext();
printNode(node);
}
else {
Object obj=root.getObj();
System.out.println("双向链表的数据元素"+obj);
LinkNode parent=root.getParent();
while(parent!=null){
Object ob=parent.getObj();
System.out.println("双向链表的数据元素"+ob);
parent=parent.getParent();
}
}
}
向双向链表中插入元素:
public static LinkNode insert(LinkNode nodee,int index){
int count=0;//标记要插入到链表中的哪个位置,根据该标记,找到相应的结点
LinkNode nodeR=creatDoubleLink(root,node);//先得到双向链表的头结点
LinkNode node1=nodeR.getNext();//得到头结点的下个结点
LinkNode last = null;//用来标记最后个结点
if(index<0){//如果插入的位置不符合要求
System.out.println("你插入的位置不对!");
}
if(index==0){//如果要插入到头结点
nodee.setNext(nodeR);
nodeR.setParent(nodee);
nodeR=nodee;
}
else{//如果插入的位置不是在头结点
while(node1!=null){//如果该结点不为空
count++;
if(node1.getNext()==null){//如果是链表的最后一个结点
last=node1;//标记该结点
}
if(count!=index){//如果还没找到要插入的位置
node1=node1.getNext();
}
else {//如果找到要插入的位置了
nodee.setNext(node1);
nodee.setParent(node1.getParent());
node1.getParent().setNext(nodee);
node1.setParent(nodee);
break;
}
}
if(count<index){//如果是插入到链表的末尾
last.setNext(nodee);
nodee.setParent(last);
}
}
return nodeR;
}
删除双向链表中的指定位置的结点:
public static LinkNode delete(int index){
int count=0;//用来标记要删除的是链表中的哪个结点
LinkNode nodeR=creatDoubleLink(root,node);//先得到双向链表的根结点
LinkNode nodee=nodeR.getNext();//得到双向链表根结点的下个结点
if(index==0){//如果要删除的是根结点
nodeR=nodeR.getNext();//将根结点移动到它的下个结点
nodeR.setParent(null);//因为是双向链表,故还需将根结点的父节点设为Null
}
else{//如果删除的不是根结点
while(nodee!=null){
count++;
if(count!=index){//还没找到索引值
nodee=nodee.getNext();//将node往后移,寻找与索引值相同的结点
}
else{
if(nodee.getNext()!=null){//如果删除的不是最后一个结点
nodee.getParent().setNext(nodee.getNext());
nodee.getNext().setParent(nodee.getParent());
}
else{//删除的是最后一个结点
nodee.getParent().setNext(null);
}
}
}
if(count<index){
System.out.println("你要删除的位置不对!");
return null;
}
}
return nodeR;
}
循环链表:当给定一个链表时,判断该链表是否有环。
建立一个循环链表:
public static LinkNode createCircleList(String [] sa){
LinkNode root=new LinkNode(sa[0]);
LinkNode node1=new LinkNode(sa[1]);
LinkNode node2=new LinkNode(sa[2]);
LinkNode node3=new LinkNode(sa[3]);
LinkNode node4=new LinkNode(sa[4]);
root.setNext(node1);
node1.setNext(node2);
node2.setNext(node3);
node3.setNext(node4);
//node4.setNext(root);
return node3;
}
判断循环链表是否有环:
public static boolean isCircle(LinkNode root){
LinkNode fast=root;
LinkNode slow=root;
while(fast!=null&&fast.getNext()!=null){
fast=fast.getNext().getNext();
slow=slow.getNext();
if(fast==slow){
return true;
}
上面写的双向链表和循环链表的创建方法有点太繁琐,稍微修改了下:
双向链表的创建:
public static LinkNode creatDoubleLink(LinkNode root,LinkNode node){
if(root!=null&&biaoji<a.length-1){//如果根结点不为空,且还没达到数组的末尾
biaoji++;//使标记元素+1
node=new LinkNode(a[biaoji]);//得到根结点的下个结点
root.setNext(node);//将两者建立关系
node.setParent(root);
LinkNode node1=null;
creatDoubleLink(node,node1);//递归调用
}
return root;
}
单向循环链表的创建:
public static LinkNode createCircleLink(LinkNode node1){
if(biaoji!=sa.length-1&&root!=null){//如果还不是最后一个结点
biaoji++;
LinkNode node=new LinkNode(sa[biaoji]);
if(biaoji==sa.length-1){
nodee=node;//标记最后一个结点
}
node1.setNext(node);//建立结点之间的关系
createCircleLink(node);//递归调用
}
else{//如果是最后一个结点
nodee.setNext(root);//将最后一个结点的后继结点设为根结点
}
return root;
}