目录
1,什么是单链表?
单链表 [LinkedList]:由各个内存结构通过一个 Next
指针链接在一起组成,每一个内存结构都存在后继内存结构【链尾除外】,内存结构由数据域和 Next 指针域组成。简单来说可以想象成下面这张图片:
- 头指针
- 在线性表的链式存储结构中,头指针是指链表指向第一个结点的指针,若链表有头结点,则头指针就是指向链表头结点的指针。
- 头指针具有标识作用,故常用头指针冠以链表的名字。
- 无论链表是否为空,头指针均不为空。头指针是链表的必要元素。
- 头结点
- 头结点是为了操作的统一与方便而设立的,放在第一个元素结点之前,其数据域一般无意义(当然有些情况下也可存放链表的长度、用做监视哨等等)。
- 有了头结点后,对在第一个元素结点前插入结点和删除第一个结点,其操作与对其它结点的操作统一了。
- 首元结点也就是第一个元素的结点,它是头结点后边的第一个结点。
- 头结点不是链表所必需的。
2,单链表的实现
2.1,单链表的节点类型
也就是定义单链表的节点类型,每一个节点包含一个data域和一个next域。
class Node{
public int data;
public Node next;//指向下一个节点
//构造器
public Node(int data) {
this.data = data;
}
@Override
public String toString() {
return "HeroNode{" +
"data=" + data +
'}';
}
}
2.2,单链表的实现
2.2.1,定义一个单链表
class SingleLinkedList{
// 先定义一个头结点,不存放具体的数据
private Node head=new Node(0);
public Node getHead() {
return head;
}
}
2.2.1,头插法插入一个节点
/**
* 采用头插法插入一个节点
* @param node 需要插入的节点
*/
public void addHead(Node node){
if(this.getHead() == null)
return;
//不空就把节点查到头处
Node node1;
node1=head.next;
head.next=node;
node.next=node1;
node1=null;
}
2.2.2,采用尾插法插入一个节点
/**
* 采用尾插法插入一个节点
* @param node 需要插入的节点
*/
public void add(Node node){
Node node1=head;//辅助节点指向头结点
// 先找到链表的最后节点,然后把新节点添加到最后
while (node1.next != null)
node1=node1.next;
// 跳出循环的时候,node指向链表的尾节点
node1.next=node;
node1=node1.next;//现在让node1指向链表的尾节点
}
2.2.3,删除单链表中的一个节点
/**
* 根据值在单链表中删除一个节点
* @param value 需要删除节点的值
* @return 删除成功就返回删除的值,删除失败就返回-1
*/
public int deleteNode(int value){
int temp;
Node node=head.next;
Node node1=head;
if(node == null)
return -1;
else {//链表不空
while (node.data != value){
if(node.next== null)//判断是否走到链表末尾
return -1;
node=node.next;
node1=node1.next;
}
// 退出循环说明链表没有此元素或者找到此元素
temp=node.data;
node1.next=node.next;
return temp;
}
}
2.2.4,打印出一条单链表
/**
* 打印单链表信息
*/
public void printLinkedList(){
// 这里让node直接指向第一个元素节点,这样就可以直接判断当前节点是否为空,然后直接打印
// 如果让node指向head头结点,那么判断的时候需要用node.next != null,链表末尾元素无法打印
Node node=head.next;
if(node!= null){
while (node!= null)
{
System.out.print(node.data+ " ");
node=node.next;
}
}
}
3,关于单链表面试题目简单练习
3.1,查找单链表中第k个节点
/**
* 题目二:查找单链表中倒数第k个节点
* @param head 单链表而定头结点
* @param k
* @return 单链表中倒数第k个节点的值
* 思路:快慢指针的应用
*/
public static int getKvalue(Node head,int k){
Node node=head.next;
Node node1=head;
int num1=0;
while (node!= null)
{
if(num1 != k-1){
num1++;
node=node.next;
}
else {
node=node.next;
node1=node1.next;
}
}
// 当循环退出的时候,node1指向第k个节点
return node1.data;
}
3.2,单链表的反转
/**
* 题目三:单链表的反转
* @param head 单链表的头结点
* 思路,采用头插入法重新建立链表
*/
public static void reverseLinkedlist(Node head){
Node node=head.next;
Node temp=new Node(0);
Node node1=temp;
if(head.next==null||head.next.next==null)
return;
while (node != null){
node1=temp.next;
temp.next=node;
node=node.next;
node1=temp;
}
head=temp;
}
3.3,从尾到头打印单链表
/**
* 题目四:从尾到头打印单链表
* @param head 单链表的头结点
*/
public void printRearToHead(Node head){
Node node=head;
if(node== null){
return;
}
printRearToHead(node.next);
System.out.print(node.data+" ");
}
3.4,合并两个有序的单链表
/**
* 合并两个有序的单链表,合并之后还是有序的
* @param head1 链表一的头结点
* @param head2 链表二的头结点
* @return 返回合并后链表的头结点
*/
public static Node mergeLinked(Node head1,Node head2){
//先判断两个链表是否有一个为空,为空就返回另一个链表头结点
if(head1.next == null)
return head1;
if(head2.next == null)
return head1;
//如果两个链表都不是空,就申请两个节点分别指向两个链表
Node node=head1.next;
// 使得head1作为合并后新链表的头节点
// 指向head1的遍历指针
Node curNode=head1;
Node node1=head2.next;
while ((node!=null)&&(node1 != null)){
if(node.data < node1.data){
curNode.next=node;
curNode=node;
node=node.next;
}else {
curNode.next=node1;
curNode=node1;
node1=node1.next;
}
}
// 循环退出,说明有一条链表已经遍历完毕
while (node !=null){
curNode.next=node;
curNode=node;
node=node.next;
}
while (node1 !=null){
curNode.next=node1;
curNode=node1;
node1=node1.next;
}
return head1;
}
3.5,测试代码
public class SingleLinkedListdemo {
public static void main(String[] args) {
SingleLinkedList singleLinkedList=new SingleLinkedList();
singleLinkedList.add(new Node(1));
singleLinkedList.add(new Node(3));
singleLinkedList.add(new Node(5));
singleLinkedList.add(new Node(7));
singleLinkedList.add(new Node(9));
// singleLinkedList.deleteNode(4);
System.out.println(singleLinkedList.deleteNode(8));
singleLinkedList.printLinkedList();
System.out.println();
System.out.println(singleLinkedList.searchLinked(4));
System.out.println(getNodeNum(singleLinkedList.getHead()));
// System.out.println();
//System.out.println(getKvalue(singleLinkedList.getHead(),1));
// 单链表的反转
//reverseLinkedlist(singleLinkedList.getHead());
//singleLinkedList.printLinkedList();
// SingleLinkedListdemo singleLinkedListdemo=new SingleLinkedListdemo();
// singleLinkedListdemo.printRearToHead(singleLinkedList.getHead().next);
// SingleLinkedList singleLinkedList1=new SingleLinkedList();
// singleLinkedList1.add(new Node(2));
// singleLinkedList1.add(new Node(4));
// singleLinkedList1.add(new Node(6));
// singleLinkedList1.add(new Node(8));
// singleLinkedList1.add(new Node(10));
// Node node=mergeLinked(singleLinkedList.getHead(),singleLinkedList1.getHead());
}
- 结果展示:
3.6,单链表的小结
- 链表的主要优势有两点:
- 一是插入及删除操作的时间复杂度为O(1)。
- 二是可以动态改变大小。
- 缺点:
- 由于其链式存储的特性,链表不具备良好的空间局部性,也就是说,链表是一种缓存不友好的数据结构
- 访问链表元素需要遍历操作,不可以随机进行访问。