当前位置: 首页 > news >正文

第三章 数据结构基础

目录

  • 第三章 数据结构基础
    • 1、链表
    • 2、栈和队列
      • 队列
    • 3、递归在程序中运行的原理
    • 4、集合与哈希

第三章 数据结构基础

1、链表

⭐️单链表
单链表是一种基本的数据结构,用于存储一系列数据元素。与数组相比,单链表在内存中不需要连续的存储空间,并且能够动态地增加或减少元素。
单链表由多个节点(Node)组成,每个节点包含两部分:

  • 数据域(Data):存储数据元素。
  • 指针域(Next):指向下一个节点的引用(或指针)。

其插入与删除时间复杂度为O(1),访问遍历顺序中第i个元素时间复杂度为O(i)。

在这里插入图片描述
一般我们用数组模拟链表。
用e数组表示数据域,r数组表示每个结点的下一个结点的位置。

e[1] = A;e[2] = B;e[3] = C;e[4] = D;
r[1] = 2;r[2] = 3;r[3] = 4;r[4] = 0;

我们会用head变量记录链表第一个变量的位置。

遍历链表的代码为:

for(int i=head;i!=0;i=r[i]){
	e[i] //
}

删除单链表的第b个结点:
假设第b个结点的前一个节点为p。直接修改r[a]=r[b]即可。如果是删除第一个节点则直接令head=r[head]即可。
在这里插入图片描述
插入单链表的第i个结点;
如果插入一个节点为头节点,新节点下标为x,则r[x]=head。若为第b个结点之后,则
r[x]=r[b],r[b]=x
在这里插入图片描述

⭐️双链表
双链表是一种比单链表更复杂的数据结构。它由多个节点组成,每个节点不仅包含指向下一个节点的指针,还包含指向前一个节点的指针。这使得在双链表中可以更方便地进行插入和删除操作。
双链表中的每个节点包含三部分:

  • 数据域(Data):存储数据元素。
  • 前指针(Prev):指向前一个节点的引用(或指针)。
  • 后指针(Next):指向下一个节点的引用(或指针)。
    在这里插入图片描述

其插入与删除时间复杂度为O(1),访问第i个元素时间复杂度为O()。

一般我们用数组模拟双链表。
用e数组表示数据域,r数组表示每个结点的下一个结点的位置,1数组表示每个数组的上一个节点的位置。
在这里插入图片描述

e[1] = A;e[2] = B;e[3] = C;e[4] = D;
r[1] = 2;r[2] = 3;r[3] = 4;r[4] = 0;
l[1] = 0,[²2] = 1;/[3] = 2;/[4] = 3

一般,我们会用head变量记录链表第一个变量的位置。tail记录最后一个变量的位置。
遍历链表的代码为:

for(int i=head;i!=0;i=r[i]){
	e[i] //
}

逆序遍历为:

for(int i=tail;i!=0;i=l[i]){
 	e[i] //
}

删除操作
假设我们要删除χ号结点。
则代码为r[l[x]]=r[x],l[r[x]]=l[x]
注意,如果∞为头结点,我们要提前修改head=r[x]。如果是尾结点则要修改成tail=l[x]
在这里插入图片描述

:插入操作:
如果我们要把下标为idx的结点插入到p结点后。
l[idx]=p,r[idx]=r[p],l[r[p]]=idx,r[p]=idx;
如果是成为头节点需要写l[head]=idx,l[idx]=0,r[idx]=head,head=idx;
尾结点需要写r[tail]=x,r[idx]=0,l[idx]=tail,tail=idx
在这里插入图片描述

🍎笔记
在这里插入图片描述

例题:2219左移右移

2、栈和队列

栈(Stack)是一种后进先出(LIFO,Last In First Out)的数据结构,意味着最后插入的元素最先被移除。

栈的基本操作

  • 压入(Push):将一个元素添加到栈的顶部。
  • 弹出(Pop):从栈的顶部移除一个元素,并返回该元素。
  • 查看顶部元素(Peek):返回栈顶元素但不移除它。
  • 检查栈是否为空(isEmpty):判断栈中是否有元素。
  • 获取栈的大小(size):返回栈中元素的数量。

模拟栈
我们可以用数组stk模拟栈,用一个top变量来维护栈顶指针。
入栈
stk[++top]=x
出栈
top--
判空
top==0
获取栈的大小
return top

static int stk[] = new int[100010];
static int top=0;

static void push(int x){
	stk[++top]=x;
}

static void pop(){
	if(isEmpty()) return;
	top--;
}

static boolean isEmpty(){
	return top==0;
}

static void query(){
	if(isEmpty(){
		out.println("empty");
	}else{
		out.println(stk[top]);
	}
}

队列

队列(Queue)是一种常见的线性数据结构,遵循 先进先出(FIFO,FirstInFirst Out) 的原则。即最先进入队列的元素会最先被移出队列。
在这里插入图片描述
队列基本操作

  • 压入(Push):将一个元素插入队尾。
  • 弹出(Pop):返回队首元素并删除。
  • 查看队首元素(Peek):返回队首元素但不移除它。
  • 检查队列是否为空(isEmpty):判断队列中是否有元素。
  • 获取队列的大小(size):返回队列中元素的数量。

模拟队列

我们可以用数组模拟队列。用head表示头指针,tail表示尾指针。
初始head=tail=0
我们插入元素时,可以写h[tail++]=x
删除元素可以写head--
判空则判断head==tail

📖
在这里插入图片描述
📚

import javax.sound.sampled.Line;
import java.math.BigInteger;

import java.util.Arrays;
import java.util.Scanner;


public class  Main {
    static int N = 100010;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int m = sc.nextInt();
        int[] a = new int[N];
        int head = 0;
        int tail = 0;

        while(m-->0){
            String str = sc.next();
            if(str.equals("push")){
                int x = sc.nextInt();
                a[tail] = x;
                tail ++;
            } else if (str.equals("pop")) {
                if(tail == head){
                    continue;
                }else{
                    a[head] = 0;
                    head ++;
                }
            } else if (str.equals("empty")) {
                if(tail == head){
                    System.out.println("YES");
                }else {
                    System.out.println("NO");
                }
            } else if (str.equals("query")) {
                if(tail == head){
                    System.out.println("empty");
                }else{
                    System.out.println(a[head]);
                }
            }
        }
    }
}

3、递归在程序中运行的原理

递归,即函数自己调用自己的一种程序。
递归(Recursion)是计算机编程中的一种重要概念,指的是一个函数直接或间接调用自身的编程技术。通过将复杂问题分解为更小的子问题来解决。
递归函数通常包含两个关键部分:
1.基准条件(BaseCase):当问题被简化到某种程度时,直接返回结果,不再调用自身,以避免无限循环。
2.递归条件(RecursiveCase):在没有达到基准条件时,函数调用自身,并逐步向基准条件靠近。

int fun(int x){
	if(x==O) return 1;
	return x*fun(x-1);
}
...
System.out.print(fun(4);
...

递归是借助栈结构进行回溯的。每当递归函数调用自身时,系统会将当前的函数调用状态(包括局部变量、参数、返回地址等)压入栈中。当递归达到基准条件(basecase)时,函数开始从栈中弹出,逐个返回调用状态,完成整个递归过程。开始时我们将fun(4),fun(3),…,fun(0)压入栈中,fun(0)是递归函数的边界,然后开始依次回溯fun(1),fun(2),…,fun(4)。
在这里插入图片描述

4、集合与哈希

什么是集合?
给定一个序列,你需要从中判断出一个元素是否存在。
暴力做法时间为O(n)。
排序后二分做法的时间复杂度为O(log)
如果将元素当作下标来存储,也就是cnt[a[司]=1表示a[]存在,时间复杂度为O(1),空间复杂度会很高。
集合便是查找某个元素的复杂度为O(1)的数据结构。其通过一个特殊的哈希函数,将数字映射到某一个位置。查找时通过该哈希函数我们就可以快速找到,一般计算出的哈希函数值不会超过元素个数。
cnt[hash(a[il])] = 1。

⭐️开放寻址法
定义一个hash函数结果为x&prime,prime是大于元素个数的第一个素数。
计算出哈希函数后,我们直接令mp[hash(∞)]=c,询问一个数是否存在时,我们直接查找
mp[hash(a)]是否是x即可。
但有一种特殊的情况,即两个数字‰prime的结果是一样的,则就会在mp数组的位置产生冲突。
我们有一种方式是如果产生冲突了,就向后继续查找第一个空,直到第一个没有被占的位置。这就是开放寻址法。

⭐️哈希表的实现
find函数的实现

static int find(int x){
	int k=getHash(x);
	while(h[k]!=-1&&h[k]!=x) k++;
	return k;
}

static void solve(){
	int n=in.nextInt();
	Arrays.fill(h,-1);
	for(int i=1;i<=n;i++){
		String op=in.next();
		if(op.equals("I")){
			int x=in.nextInt();
			int k=find(x);
			h[k]=X;
		}else[
			int x=in.nextInt();
			int k=find(x);
			if(h[k]==x){
				out.println("Yes");
			}else{
				out.println("No");
			}
		}
	}
}

⭐️Java提供的Set集合

1️⃣HashSet
HashSet是基于哈希表(HashTable)实现的集合。它使用哈希函数来存储元素,并不保证元素的顺序。

特点:

  • 不允许有重复的元素。
  • 插入、删除和查找操作的时间复杂度平均为O(1)。
  • 元素的存储顺序与插入顺序无关。

常用函数

  • add(E e):向集合中添加元素,如果元素已存在,返回false。
  • remove(Object o):从集合中移除元素,成功返回true。
  • contains(Object o):判断集合中是否包含指定元素。
  • size():返回集合中元素的数量。
  • clear():清空集合中的所有元素。
  • isEmpty():判断集合是否为空。
import java.util.HashSet;
public class HashSetExample{
	public static void main(String[] args) {
		HashSet<String> set = new HashSet<>();
		//添加元素
		set.add("apple" );
		set.add("banana");
		set.add("cherry");
		//检查是否包含元素
		System.out.println(set.contains("banana"); // 输出 true
		// 移除元素
		set.remove("banana");
		//集合大小
		System.out.println(set.size()); // 输出 2
	}
}

2️⃣LinkedHashSet
LinkedHashSet是HashSet的子类,它基于哈希表实现,同时使用链表来维护元素的插入顺序。因此,它既具备HashSet的哈希表特性,又能保证元素按照插入顺序排列。

特点

  • 不允许有重复的元素。
  • 保证元素的插入顺序。
  • 插入、删除和查找操作的时间复杂度平均为(O(1))。
  • 适用于既希望利用哈希表的高效性能,又需要保留元素插入顺序的场景。

常用函数

  • add(E e):添加元素。
  • remove(object o):移除元素。
  • contains(object o):检查是否包含某元素。
  • size():返回元素个数。
  • clear():清空集合。
  • isEmpty():判断集合是否为空。
import java.util.LinkedHashSet;
public class LinkedHashSetExample {
	public static void main(String[] args){
		LinkedHashSet<String> set = new LinkedHashSet<>();
		// 添加元素
		set.add("apple" );
		set.add("banana");
		set.add("cherry");
		//输出集合,按插入顺序
		for (String s : set) {
		System.out.println(s); //输出顺序为:apple,banana,cherry
		}
		// 检查是否包含元素
		System.out.println(set.contains("banana")); // 输出 true
	}
}

3️⃣TreeSet
TreeSet是基于红黑树(Red-Black Tree)实现的集合。与HashSet和LinkedHashSet不同,TreeSet会自动对元素进行排序。它实现了SortedSet接口,因此可以保证集合中的元素处于自然顺序(或者是用户自定义的排序规则)。

特点

  • 不允许有重复的元素。
  • 元素按照自然顺序(或自定义顺序)排序。
  • 插入、删除和查找操作的时间复杂度为O(logn)。
  • 适用于需要保持元素有序的场景。

常用函数

  • add(E e):添加元素。
  • remove(Object o):移除元素。
  • contains(Object o):检查是否包含某元素。
  • size():返回集合中元素的数量。
  • isEmpty():判断集合是否为空。
  • first():返回集合中的第一个元素(最小的元素)。
  • last():返回集合中的最后一个元素(最大的元素)。
import java.util.TreeSet;
public class TreeSetExample{
	public static void main(String[] args) {
		TreeSet<Integer> set = new TreeSet<>();
		//添加元素
		set.add(30);
		set.add(10);
		set.add(20);
		set.add(40);
		//自动排序
		System.out.println(set); // 输出 [10,20, 30,40]
		//获取第一个和最后一个元素
		System.out.println("最小值:"+ set.first()); // 输出10
		System.out.println("最大值:"+ set.last(); // 输出40
		//获取子集
		System.out.println("小于 30 的元素: " + set.headSet(30)); // 输出 [10,20]
		System.out.println("大于等于20的元素:"+ set.tailSet(20));// 输出[20, 30,40]
	}
}

⭐️Java提供的Map哈希表
在Java中,Map是一种用于存储键值对<key,value>的数据结构,每个键都唯一对应一个值,允许通过键来快速查找、更新和删除值。Java的Map接口有多种常见实现,如HashMap、LinkedHashMap和TreeMap。

1️⃣HashMap
HashMap是基于哈希表实现的Map,键值对存储无序。

特点

  • 基于哈希表实现,键通过哈希函数映射到哈希表中的位置。
  • 查找、插入、删除操作的平均时间复杂度为O(1)。
  • 无序存储,元素的顺序不保证与插入顺序一致。

常用函数

  • put(K key,Vvalue):向Map中添加键值对,如果键已存在,更新对应的值。
  • get(Object key):根据键查找对应的值。
  • remove(Object key):移除指定键对应的键值对。
  • getOrDefault(Object key,0):根据键查找对应的值,不存在返回0。
  • containsKey(Object key):判断是否包含指定的键。
  • size():返回Map中的键值对数量。
  • clear():清空所有键值对。
  • keySet():返回所有键的集合。
import java.util.HashMap;
public class HashMapExample {
	public static void main(String[] args) {
		//创建一个HashMap
		HashMap<String, Integer> map = new HashMap<>();
		//添加键值对
		map.put("apple",1);
		map.put("banana",2);
		map.put("cherry", 3);
		11获取值
		System.out.println("apple 的值: " + map.get("apple"); // 输出 1
		//判断是否包含键
		System.out.println("是否包含cherry:"+ map.containsKey("cherry");// 输出 true
		//移除键值对
		map.remove("banana" );
		1//获取键的集合
		System.out.println("键的集合: " + map.keySet()); // 输出[apple,cherry]
		//获取值的集合
		System.out.println("值的集合:" + map.values(); // 输出[1,3]
		// 获取 Map 大小
		System.out.println("Map 大小:" + map.size()); // 输出 2
	}
}

遍历哈希表

HashMap<String, Integer> map = new HashMap<>();
for(String key:map.keySet()){
	//key和map.get(key);
}

2️⃣LinkedHashMap
LinkedHashMap是HashMap的子类,除了具有HashMap的所有特性外,还维护了一个双向链表,记录元素的
插入顺序或访问顺序。

  • 特点
    • 元素按插入顺序(或访问顺序)排列。
    • 性能与HashMap类似,但由于维护链表,性能略微降低。
    • 适用于既希望保持键值对有序,又需要快速查找的场景。
import java.util.LinkedHashMap;
public class LinkedHashMapForEachExample{
	public static void main(String[] args) {
		//创建一个LinkedHashMap,默认按插入顺序排列
		LinkedHashMap<String, Integer> linkedHashMap = new LinkedHashMap<>();
		
		//添加键值对
		linkedHashMap.put("apple",1);
		linkedHashMap.put("banana", 2);
		linkedHashMap.put("cherry",3);
		
		//使用forEach遍历键值对
		System.out.printIn(“使用forEach遍历LinkedHashMap(按插入顺序):");
		linkedHashMap.forEach((key, value) -> {
			System.out.println(key + ": " + value);
		});
	}
}

3️⃣TreeMap
TreeMap是基于红黑树实现的有序Map,键值对按照键的自然顺序或自定义比较器排序。

  • 特点
    • 元素按键的自然顺序(或自定义顺序)排序。
    • 查找、插入和删除的时间复杂度为O(1ogn)。
    • 不允许存储null键(会抛出NullPointerException)。
    • 适用于需要对键进行排序的场景。
import java.util.TreeMap;
public class TreeMapExample {
	public static void main(String[] args) {
		//创建一个TreeMap
		TreeMap<String, Integer> map = new TreeMap<>();
		/添加键值对
		map.put("banana", 2);
		map.put("apple", 1);1
		map.put("cherry",3);
		//按照键的自然顺序排序
		System.out.println("TreeMap 键值对:"+ map); // 输出 {apple=1,banana=2,cherry=3}
		//获取第一个和最后一个键
		System.out.println("第一个键:"+ map.firstkey());//输出apple
		System.out.println("最后一个键:"+ map.lastKey());//输出cherry
	}
}

相关文章:

  • 云原生周刊:Istio 1.25.0 正式发布
  • PPT内视频播放无法播放的原因及解决办法
  • Maven Deploy Plugin如何使用?
  • c++介绍锁 一
  • STM32基础教程--旋转编码器计数实验
  • 一文了解汽车图像传感器
  • 爱普生可编程晶振SG-8200CJ特性与应用
  • 狮子座大数据分析(python爬虫版)
  • 前端开发定时器的一些规范使用
  • 数据库的搭建
  • SpringBoot3.3.0集成Knife4j4.5.0实战
  • KidneyTalk-open系统,RAG在医疗场景的真实落地:用于解决肾脏疾病的医疗问答问题
  • vue3深入组件——依赖注入
  • 基于YOLO(以YOLOv8为例)模型开发算法的详细步骤,包含算法代码、训练指导、数据集准备以及可能的改进方向
  • 【C】链式二叉树算法题2
  • C# Channel
  • 【性能测试】Jmeter下载安装、环境配置-小白使用手册(1)
  • 国产编辑器EverEdit - 脚本(解锁文本编辑的无限可能)
  • Python开发Scikit-learn面试题及参考答案
  • 下载WindTerm学习的二三知识
  • 达恩当选罗马尼亚总统
  • 网约车司机猝死,平台和保险公司均拒绝赔偿,法院判了
  • 俄乌上周在土耳其直接谈判,外交部回应
  • AI赋能科学红毯,机器人与科学家在虚实之间叩问“科学精神”
  • 新华时评:博物馆正以可亲可近替代“高冷范儿”
  • 女生“生理期请病假要脱裤子证明”?高校回应:视频经处理后有失真等问题