链表(Linked List)day03

本文深入解析链表数据结构,包括单链表、双链表及环形链表的概念、特点与应用场景,并提供Java与C语言实现示例,涵盖增删改查等基本操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

链表(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);

运行结果:
在这里插入图片描述

双向链表

管理单项链表的缺点分析:

  1. 单项链表查找只能一个方向,双向链表可以向前或向后
  2. 单项链表不能自我删除,要借助辅助节点(temp),双向链表可以自我删除
    在这里插入图片描述
  3. 遍历:和单链表一样,可以向前或向后
  4. 添加:(默认添加到最后) temp.next=newNode;newNode.pre=temp;
  5. 修改:和单链表一样
  6. 删除:直接找到要删除的节点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);
	}

}

运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值