链表的基本结构
public class LNode {
public String data;//节点数据
LNode next;//下一个节点
}
这是最基本的链表结构,还有双向链表和循环链表会在文章最后提到。
建立单链表
头部插入建立单链表
头部插入建立方式的思想是,首先建立一个指针,
1.然后新建第一个节点,让指针指向这个节点
2.然后再新建一个节点,通过指针拿到第一个点的地址,让新建的下一个地址指向第一个节点。
3.最后把指针移动到第一个节点上。
后面重复这个过程就可以
大家可以根据这张图来理解一下。
下面是实现的代码
// 在头部插入
public LNode Creat_LinkListHead() {
LNode head = null;// 头部,空表
System.out.println("现在使用头部插入法,请输入数据,以0为结束");
String newDate;
while (!(newDate = in.next()).equals("0")) {
LNode newNode = new LNode();//新建一个节点
newNode.data = newDate;
newNode.next = head;//通过指针找到上一个节点地址,让新节点的下一个地址指向新节点
head = newNode;//让指针移动到新建的节点上
}
System.out.println("录入完毕");
return head;
}
在尾部插入建立链表
在头部插入的方式,有一个很大的问题,就是最后我指针的位置,在整个链表逻辑上
的最后位,遍历的时候,会反过来,比方说建立的顺序是ABCDE,遍历的时候就是EDCBA了
在尾部插入建立的方式就是为解决这个问题
尾部插入的思路是:首先我们建立一个头节点和一个尾节点,让头节点和尾节点一起指向第一个节点,之后的话头结点不再变动,尾节点用头部插入的方式建立起链表,可以发现这个方式和头部插入没有什么区别,主要就是增加了一个头节点。
下面是实现的代码
// 在尾部插入
public LNode Creat_LinkListTail() {
LNode H = null;// 头指针
LNode R = null;// 尾指针
String newDate;
System.out.println("现在使用尾部插入法,以0为结束");
while (!(newDate = in.next()).equals("0")) {
LNode newNode = new LNode();
newNode.data = newDate;
// 第一个指针
if (H == null) {
H = newNode;
} else {
R.next = newNode;
}
//指针移动到创建的新节点
R = newNode;
}
//让随后一个节点指向null
if (R != null)
R.next = null;
return H;
}
带头节点的尾部插入
上面方式也有一个问题,第一个节点由于没有前驱,所以我们在很多操作的时候都要考虑这个特殊的点,这样在增加删除的时候都会比较麻烦,比较好的解决方式,我们可以第一个节点前面在加一个节点,这个节点没有数据,只是指向原来的第一个节点。如下图
H代表的是头指针
R代表的是尾指针
新增一个头结点后就可以很好的解决这个问题,但在遍历,定位节点的时候要注意哥不带头结点的链表的区别,这个头结点 只是用来作为前驱存放原本第一个节点的地址的。
public LNode Creat_LinkListTailWithHead() {
LNode H = null;//头指针
LNode R = null;//尾指针
//建立头结点
LNode headNode = new LNode();
headNode.next = null;
H = headNode;
R = headNode;
System.out.println("现在使用带头结点的的链表尾部插入,输入0为结束");
String newDate;
while(!(newDate=in.next()).equals("0")){
LNode newNode = new LNode();
newNode.data = newDate;
newNode.next = null;
R.next = newNode;
R=newNode;
}
R.next = null;
return H;
}
遍历链表
遍历的思路是从头结点开始,输出节点的数据, 之后让指针移动到下一个节点
//遍历链表
public void look(LNode head) {
if (head == null) {
System.out.println("链表为空");
System.exit(0);
}
LNode flag = head;//用一个指针来拿到头结点的地址
do {
System.out.print(flag.data);
flag = flag.next;//下移到下一个节点
} while (flag != null);//如果节点为空结束循环
}
当然这个遍历是有问题的,没有判断链表有没有带头结点,大家可以自行修改一下。
计算链表长度
这个的思路的遍历差不多,只不过我们需要定义一个变量用来存储长度
//计算带头结点的链表的长度
public int lenth_haveHead(LNode H){
int len = 0 ;//用来存放长度
while(H.next!=null){
H=H.next;
len++;
}
return len;
}
//不带头结点的链表的长度
public int lenth_nothaveHead(LNode H){
int len = 0;//用来存放长度
//判断链表是否为空
if(H==null){
System.out.println("链表为空");
return 0;
}
len = 1;
while(H.next!=null){
H = H.next;
len++;
}
return len;
}
可以看出,带头节点的链表在遍历上就比不带的方便很多。
查询指定节点
//查询指定节点
//n 代表第几个节点
public LNode findLNode(int n,LNode H){
LNode myH;
if(H.data == null){
//带头结点
myH = H.next;
}else{
//不带头结点
myH = H;
}
int len = 0;
while(myH!=null){
len++;
if(len == n) break;
myH = myH.next;
}
return myH;
}
首先我们知道带头节点的链表的第一个节点,没有数据,用这个作为标志来判断链表是哪种链表。
如果是带头结点的,那链表的第一位应该是头结点.next
后排插入
后排插入就是在一个节点的后面,插入一个新节点
思路如下
我们先找到这个节点
通过这个节点的next得到下一个节点的地址,让新节点的next指向下一个节点。
让被插的节点将next指向新的节点
下面给出实现代码
//在第n的节点后插入data
public void insertBehind(String data,int n,LNode H){
MyLinkList list = new MyLinkList();
LNode newNode = new LNode();//新建节点
newNode.data = data;
newNode.next = null;
LNode node = list.findLNode(n, H);//找到要插入的位置
if(node == null) {
System.out.println("找不到节点");
return;
}
//获取前驱的next,也就是下一个节点
newNode.next = node.next;
//让前驱指向新的节点
node.next = newNode;
}
前排插入
前排插入的思想和后排插入差不多,只不过我们再拿到节点后,需要再去查找一次,找到这的节点的前驱,之后以这个前驱做后排插入就行了。
/*
* 在第n个节点前面插入一个节点
* */
public void insertFront(String data,int n,LNode H){
LNode newNode = new LNode();
newNode.data = data;
newNode.next = null;
MyLinkList list = new MyLinkList();
//找到要插入的节点
LNode node = list.findLNode(n, H);
if(node==null){
System.out.println("找不到节点");
return;
}
//找到node节点的前驱
LNode myH = H;
while(myH.next!=node){
myH = myH.next;
}
newNode.next = node;
myH.next = newNode;
}
删除节点
删除节点的思路如下:
找到要删除的节点
再找到他的前驱节点
让前驱的next指向这个节点的next,这个时候要删除的节点已经被架空了
最后我们给这个要删除的节点赋值null 释放空间
MyLinkList list = new MyLinkList();
LNode node = list.findLNode(n, H);
if(node == null) {
System.out.println("找不到要删除的节点");
return;
}
//找到这个节点的前驱
LNode frontNode = list.findLNode(n-1, H);
if(frontNode==null){
System.out.println("找不到前驱");
return;
}
frontNode.next = node.next;
node = null;
}
循环链表
循环链表就是将最后一个节点,原来指向空,改成指向头部,实质上并没有什么区别,主要是判断是不能用null来做链表结束的条件了
双向链表
单向的连接再很多时候会有麻烦,比如我们的前排插入,我们要得到前驱,不得不再做一次遍历,双向链表正是为了解决这个问题,我们再结构中再加一个指针让他指向前驱就可以了。