链表(Linked List)
链表介绍
链表是有序列表,在内存中存储如下:
- 链表是以节点的方式来存储,是链式存储
- 每个节点包含data域、next域:指向下一个节点
- 链表的各个节点不一定是连续存储
- 链表分带头结点链表和没有头结点链表
单链表(带头结点)逻辑结构如下:
单链表
使用带head头的单项链表实现增删改查:
package com.xhl.LinkedList;
public class Node {
public int data;
public Node next;
//构造器
public Node(int data) {
super();
this.data = data;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "Node[data="+data+"]";
}
}
```java
package com.xhl.LinkedList;
public class SingleLinkedList {
//初始化头结点
Node head = new Node(0);
//添加节点到单项链表
public void add(Node node) {
//因为head节点不能动,用temp辅助遍历
Node temp = head;
//遍历链表,找到最后的节点
while(temp.next!=null) temp = temp.next;
temp.next = node;
}
//在带头结点的单链表第i个位置前插入,head为0位置
public void addBynum(Node node , int i) {
Node temp = head;
int j=1;
//遍历链表,找到第i-1的节点
while(temp.next!=null&&j<i) {
temp = temp.next;
j++;
}
if(i<1||j!=i) {
System.out.println("位置无效");
return;
}
node.next = temp.next;
temp.next = node;
}
//更改
public void update(int i,int n) {
Node temp = head;
int j=0;
//遍历链表,找到第i的节点
while(temp.next!=null&&j<i) {
temp = temp.next;
j++;
}
if(i<1||j!=i) {
System.out.println("位置无效");
return;
}
temp.data = n;
}
//删除第i个节点,并返回值
public int delete(int i) {
Node temp = head;
int j=1;
//遍历链表,找到第i-1的节点
while(temp.next!=null&&j<i) {
temp = temp.next;
j++;
}
if(i<1||j!=i) {
throw new RuntimeException("位置无效");
}
Node del = temp.next;
temp.next = del.next;
return del.data;
}
//显示链表
public void show() {
Node temp = head;
//遍历链表,找到最后的节点
while(temp.next!=null) {
temp = temp.next;
System.out.println(temp.toString());
}
System.out.println();
}
}
package com.xhl.LinkedList;
public class SingleLinkedListDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
Node node1 = new Node(2);
Node node2 = new Node(33);
Node node3 = new Node(244);
Node node4 = new Node(5098);
//创建链表
SingleLinkedList sl = new SingleLinkedList();
sl.add(node1);
sl.add(node2);
sl.add(node3);
sl.add(node4);
//show
sl.show();
sl.update(2, 44);
sl.show();
sl.addBynum(new Node(23), 3);
sl.show();
sl.delete(1);
sl.show();
}
}
运行结果:
C语言版本:
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;//设置ElemType类型
typedef struct LNode
{
ElemType data;
struct LNode *next=NULL;
}LNode;
LNode* CreateNode(ElemType data){
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = data;
node->next = NULL;
return node;
}
void add(LNode *L , LNode *node){
LNode *temp= L;
while(temp->next!=NULL){
temp = temp->next;
}
temp->next = node;
}
void addBynum(LNode *L , LNode *node,int i){
LNode *temp= L;
int j=1;
while(temp->next!=NULL && j<i){
temp = temp->next;
j++;
}
if(i<1||j!=i) {
printf("位置无效\n");
return;
}
node->next = temp->next;
temp->next = node;
}
void update(LNode *L,int i,ElemType n) {
LNode *temp = L;
int j=0;
//遍历链表,找到第i的节点
while(temp->next!=NULL &&j<i) {
temp = temp->next;
j++;
}
if(i<1||j!=i) {
printf("位置无效\n");
return;
}
temp->data = n;
}
ElemType del(LNode *L,int i) {
LNode *temp = L;
int j=1;
//遍历链表,找到第i-1的节点
while(temp->next!=NULL &&j<i) {
temp = temp->next;
j++;
}
if(i<1||j!=i) {
printf("位置无效\n");
}
LNode *del = temp->next;
temp->next = del->next;
return del->data;
}
void show(LNode *L) {
LNode *temp = L;
while(temp->next!=NULL) {
temp = temp->next;
printf("%d\n",temp->data);
}
printf("\n");
}
int main()
{
LNode L;
LNode *a = CreateNode(2);
LNode *b = CreateNode(33);
LNode *c = CreateNode(444);
LNode *d = CreateNode(5098);
add(&L,a);
add(&L,b);
add(&L,c);
add(&L,d);
show(&L);
update(&L,2,44);
show(&L);
LNode *e = CreateNode(23);
addBynum(&L,e,3);
show(&L);
printf("删除数据:%d\n",del(&L,1));
show(&L);
}
运行结果:
应用
//单链表面试题
/*
* 1、求单链表中有效节点个数(如果是带头结点的链表,不统计头结点)
*/
public static int getLength(Node head) {
if(head.next==null) {
return 0;
}
int length = 0;
//定义一个辅助变量
Node temp = head.next;
while(temp != null) {
temp = temp.next;
length ++;
}
return length;
}
/*
* 2、查找单链表中的倒数第k个结点【新浪面试】
* 思路
* 编写一个方法接受head节点,同时接受一个index
* index表示是倒数第index个节点
* 先得到链表的getLength
* 遍历到第(size-index)个
*/
public static Node findLastIndexNode(Node head,int index) {
//判断如果链表为空,返回null
if(head.next==null) {
return null;
}
int size = getLength(head);
//判断index是否在合理范围内
if(index<=0 || index>size) {
return null;
}
Node temp = head.next;//size = 3 index=1 3-1=2
for(int i=0;i<size-index;i++) {
temp = temp.next;
}
return temp;
}
/*
* 3、单链表的反转【腾讯面试】
* 思路:
* 定义一个节点 newhead
* 遍历一遍的链表,把每个节点取出放入new的最前端
* head。next= newhead.next
*/
public static void reversetList(Node head) {
if(head.next==null || head.next.next==null) {
return;
}
Node newhead = new Node(0);
//定义辅助变量用于遍历
Node temp = head.next;//带头结点
//指向当前节点temp的下一个节点
Node next = null;
while(temp != null){
next = temp.next;//记录temp.next
temp.next = newhead.next;//插入head最前端的第一步
newhead.next = temp;
temp = next ;//temp 后移
}
//将 head.next指向 newhead.next
head.next = newhead.next;
}
/*
* 4、从尾到头打印单链表【百度,要求方式1:反向遍历 2stack栈】
* 思路:
* 1、将单链表反转再遍历,但会破坏原有结构no
* 2、利用栈,将各个节点打入栈中
*/
public static void reversePrint(Node head) {
if(head.next == null) {
return;
}
Stack<Node> stack = new Stack();
Node temp = head.next;
while(temp != null) {
stack.push(temp);
temp = temp.next;
}
while(stack.size()>0) {
System.out.println(stack.pop());
}
}
main
sl.show();
System.out.println("节点个数:"+SingleLinkedList.getLength(sl.head));
Node test = SingleLinkedList.findLastIndexNode(sl.head, 2);
System.out.printf("倒数第2个数为Node[data=%d]\n",test.data);
SingleLinkedList.reversetList(sl.head);
sl.show();
SingleLinkedList.reversePrint(sl.head);
运行结果:
双向链表
管理单项链表的缺点分析:
- 单项链表查找只能一个方向,双向链表可以向前或向后
- 单项链表不能自我删除,要借助辅助节点(temp),双向链表可以自我删除
- 遍历:和单链表一样,可以向前或向后
- 添加:(默认添加到最后) temp.next=newNode;newNode.pre=temp;
- 修改:和单链表一样
- 删除:直接找到要删除的节点temp
temp.pre.next = temp.next;
temp.next.pre = temp.pre;
package com.xhl.DoubleLinkedList;
public class Node {
public int data;
public Node next;
public Node pre;//前一个节点
//构造器
public Node(int data) {
super();
this.data = data;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "Node[data="+data+"]";
}
}
package com.xhl.DoubleLinkedList;
public class DoubleLinkedList {
//初始化头结点
private Node head = new Node(0);
public Node getHead() {
return head;
}
//遍历双向链表
public void showList() {
//判断链表是否为空
if(head.next==null) {
System.out.println("链表为空");
return ;
}
Node temp = head.next;
while(temp != null) {
System.out.println(temp);
temp = temp.next;
}
System.out.println();
}
//添加节点到最后
public void addNode(Node node) {
Node temp = head;
while(temp.next !=null) {
temp = temp.next;
}
temp.next = node;
node.pre = temp;
}
//修改节点的内容
//位置i的node。data改为n(head为位子0;
public void updataNode(int i ,int n) {
Node temp = head;
int j=0;
//遍历链表,找到第i的节点
while(temp.next!=null&&j<i) {
temp = temp.next;
j++;
}
if(i<1||j!=i) {
System.out.println("位置无效");
return;
}
temp.data = n;
}
//删除
public void deleteNode(int i) {
Node temp = head;
int j=0;
while(temp.next!=null&&j<i) {
temp = temp.next;
j++;
}
if(i<1||j!=i) {
System.out.println("位置无效");
return;
}
temp.pre.next = temp.next;
temp.next.pre = temp.pre;
}
//在指定位置添加,位置i前添加节点
public void addBynum(Node node ,int i) {
Node temp = head;
int j=0;
while(temp.next != null && j<i) {
temp = temp.next;
j++;
}
if(i<1||j!=i) {
System.out.println("位置无效");
return;
}
temp.pre.next = node;
node.pre = temp.pre;
temp.pre = node;
node.next = temp;
}
}
package com.xhl.DoubleLinkedList;
public class DoubleLinkedListDome {
public static void main(String[] args) {
// TODO Auto-generated method stub
Node node1 = new Node(2);
Node node2 = new Node(33);
Node node3 = new Node(244);
Node node4 = new Node(5098);
DoubleLinkedList dl = new DoubleLinkedList();
dl.addNode(node1);
dl.addNode(node2);
dl.addNode(node3);
dl.showList();
dl.addBynum(node4, 3);
dl.showList();
dl.updataNode(1, 5);
dl.showList();
dl.deleteNode(2);
dl.showList();
}
}
单向环形链表的应用场景
约瑟夫问题:
设编号为1、2、3、……,n的n个人围坐一圈,约定编号为k的人从1开始报数,数到m的那个人人出列,他们的下一位又从1开始报数,数到m再次出列。直到所有人都出列
n=5 即有5个人
k=1 即 从第一个人开始报数
m=2 数2下
package com.xhl.CircleLinkedList.Josepfu;
public class Kid {
private int no;
private Kid next;
public Kid(int no) {
super();
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Kid getNext() {
return next;
}
public void setNext(Kid next) {
this.next = next;
}
}
package com.xhl.CircleLinkedList.Josepfu;
public class CircleLinkedList {
//创建一个first节点,没有编号
private Kid first = null;
// 添加小孩节点,构建一个有nums个小孩的环形链表
public void addKid(int nums) {
if(nums<1) {
System.out.println("nums值无效");
return ;
}
// 辅助指针
Kid tempKid = null;
for(int i=1 ;i<= nums ; i++) {
Kid kid = new Kid(i);
if(i==1) {
first = kid;
first.setNext(first);
tempKid = first;
}else {
tempKid.setNext(kid);
kid.setNext(first);
tempKid = kid;
}
}
}
// 遍历当前的环形链表
public void showKid() {
if(first == null) {
System.out.println("没有小孩!!");
return ;
}
Kid temp = first;
do {
System.out.println("小孩的编号:"+temp.getNo());
temp = temp.getNext();
}while(temp != first);
System.out.println();
}
//根据用户输入计算出圈顺序
public void countKid(int startNo, int countNum ,int nums) {
if(first == null || startNo <1 || startNo > nums) {
System.out.println("数据无效");
return ;
}
// 辅助指针
Kid helper = first;
// helper需要指向最后一个节点
while(helper.getNext() != first) {
helper = helper.getNext();
}
// 从第k-1个小孩开始报数,first和helper移动k-1次
for(int i=1;i<startNo;i++) {
first = first.getNext();
helper = helper.getNext();
}
// 当小孩报数时,first和helper同时移动m-1 first指向的小孩出去
// 当first==helper停止循环
while(first != helper){
for(int i=1;i<countNum;i++) {
first = first.getNext();
helper = helper.getNext();
}
System.out.println("小孩"+first.getNo()+"出圈");
first = first.getNext();
helper.setNext(first);
}
System.out.println("小孩"+first.getNo()+"出圈");
}
}
package com.xhl.CircleLinkedList.Josepfu;
public class Josepfu {
public static void main(String[] args) {
// TODO Auto-generated method stub
CircleLinkedList cll = new CircleLinkedList();
cll.addKid(5);
cll.showKid();
cll.countKid(1, 2, 5);
}
}
运行结果: