数据结构与算法 -- 基础(一)

数据结构与算法 – 基础(一)

1.什么是数据结构

1.数据结构是一种存储数据的方式,分为线性结构和非线性结构;
2.线性结构:

  1. 最常用的数据结构,其特点就是数据之间存在一对一的线性关系
  2. 有顺序存储结构和链式存储结构两种,顺序存储的线性表称为顺序表,顺序表中存储的元素是连续的
  3. 链式存储的线性表称为链表,链表存储的元素不一定是连续的,元素节点存放了数据元素和相邻元素的地址信息
  4. 线性结构常见的有数组 、队列 、链表和栈

3.非线性结构有:二维数组 、多维数组 、广义表 、树结构 、图结构

2.稀疏数组和队列

1.稀疏数组

1.应用场景:
在这里插入图片描述
2.稀疏数组:当一个数组中大部分元素为0或者为同一个值时可以使用稀疏数组来保存该数组
3.稀疏数组的处理方法:

  1. 稀疏数组固定有三列,总行数为目标数组中不同值的个数加一(因为稀疏数组是从第二行开始记录不同值的信息的)
  2. 对于第一行:第一列记录目标数组有多少行,第二列记录目标数组有多少列,第三列记录目标数组有多少个不同值
  3. 对于从第二行开始,第一列记录某个不同值的行数,第二列记录某个不同值的列数,第三列记录某个不同值的数值大小
    如下图:
    在这里插入图片描述
    3.代码实现:
//以上面的五子棋为应用场景
// 普通二维数组转稀疏数组
public int[][] parseToSparseArray(int[][] array) {
	//1.根据目标数组定义稀疏数组
	int[][] sparseArr = new int[array.length + 1][3]; // array.length 指的是二位数组的行数
	//2.计算出目标数组中不同值的个数
	int sum = 0;
	for(int[] row : array) {
		for(int data : row) {
			if(data != 0) {
				sum ++;
			}
		}
	}
	//3.给稀疏数组的第一行赋值
	sparseArr[0][0] = array.length;
	sparseArr[0][1] = array[0].length; // array[0].length 指的是二维数组的列数
	sparseArr[0][2] = sum + 1;
	//4.遍历二维数组给稀疏数组的其他行赋值
	int k = 1;
	for(int i = 0; i < array.length; i++) {
		for(int j = 0; j < array[0].length; j++) {
			if(array[i][j] != 0) {
				sparseArr[k++][0] = i;
				sparseArr[k++][1] = j;
				sparseArr[k++][0] = array[i][j];
			}
		}
	}
	return sparseArr;
}
//稀疏数组转二维数组
public int[][] parseToArray(int[][] sparseArr) {
	//1.根据稀疏数组创建二维数组
	int[][] arr = new int[sparseArr[0][0]][sparseArr[0][1]];
	//2.从稀疏数组的第二行开始遍历给二维数组赋值
	for(int i = 1; i < sparseArr.length) {
		arr[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
	}
	return arr;
}
2.队列

1.应用场景:
在这里插入图片描述
2.队列:

  1. 是一个有序列表,可以使用数组或链表实现
  2. 遵循先进先出的原则

3.使用环形数组模拟队列思路

  1. 环形数组是在数组基础上使用取模来实现的
  2. 有三个变量 front 、rear 、maxSize
  3. front 指向队列的第一个元素,即初始化值为 0,用于取出元素
  4. rear 指向队列最后一个元素的后一个元素,即初始化值也为 0,用于添加元素
  5. maxSize 为数组的初始化大小(即实际数组的长度,而环形数组通过取模实现了无限长度)
  6. 当 front == rear 时,队列为空
  7. 当尾索引的下一个为头索引时,队列为满,即 (rear + 1) % maxSize == front
  8. 队列中有效的元素个数为 (rear + maxSize - front) % maxSize
// 用环形数组实现队列类
class Queue {
	private int maxSize;
	private int[] arr;
	private int front = 0;
	private int rear = 0;
	//通过构造方法创建数组,并初始化 maxSize 的值
	public Queue(int maxSize) {
		this.maxSize = maxSize;
		arr = new int[maxSize];
	}
	//判断队列是否为空
	public boolean isEmpty() {
		return front == rear;
	}
	//判断队列是否为满
	public boolean isFull() {
		return (rear + 1) % maxSize == front;
	}
	//添加数据到队列
	public void add(int num) {
		if(isFull) {
			System.out.println("队列已满,无法添加数据!");
			return;
		}
		//添加数据
		arr[rear] = num;
		//添加后,rear 向后移,但是要注意取模(在这里实现了环形数组),否则数组会越界
		rear = (rear + 1) % maxSize;
	}
	//获取队列中的数据,与查询不同,取出后逻辑上我们认为该元素在数组中已经不存在了(实际上还是存在,不过会在后续的添加中被其他元素覆盖掉)
	public int get() {
		if(isEmpty) {
			throw new RuntimeException("队列为空,无法取出数据!");
		}
		int val = arr[front];
		//同样要注意取模
		front = (front + 1) % maxSize;
		return val;
	}
	//获取该队列中的有效元素个数
	public int size() {
		return (rear + maxSize - front) % maxSize;
	}
	//遍历队列
	public void showQueue() {
		if(isEmpty) {
			System.out.println("队列为空,没有数据!");
			return;
		}
		//遍历时从front所指向的元素开始,因为有size()个元素,所以遍历size()次
		for(int i = front; i < front + size(); i++) {
			//这里要注意取模
			System.out.printf("arr[%d]=%d\n",i % maxSize,arr[i % maxSize]);
		}
	}
	//查询队列头元素,不是取出数据
	public int getHead() {
		if(isEmpty) {
			System.out.println("队列为空!");
		}
		return arr[front];
	}
}

3.链表

1.链表是有序的列表,但在内存中的存储如下:
在这里插入图片描述

1.单向链表

1.单链表(带表头)的逻辑结构如下:
在这里插入图片描述
2.应用实例:使用单向链表完成对水浒英雄的增删改查
思路:

  1. 直接在链表的尾部添加,先用指针遍历,直至该节点的下一个节点为NULL,那么该节点为链表最后一个节点,然后把要添加的节点设置为该节点的下一个节点
  2. 按顺序(这里是按英雄的排名)把要添加的节点插入到链表中,先用指针遍历,每遍历一个节点,就拿该节点的排序号(这里是 no)与要添加的节点的排序号比较,若大于后者,则将要添加的节点插入到该节点前面,若两者等于,则输出该节点已存在,并退出添加方法,否则就继续向后一个节点移动,直到找到要插入的位置或者遍历到链表末尾(这种情况直接插入到最后一个节点后面就行)
  3. 修改节点,先用指针遍历,若遍历的节点的排序号和传入的节点的排序号相等,则找到,并修改节点,否则继续向下遍历直到找到并修改或者**遍历到链表的末尾仍然找不到 **(这种情况就输出未找到节点,并退出方法)
  4. 删除节点,用指针遍历,若遍历的节点与输入的排序号相等,则删除该节点,否则继续遍历直到找到或者遍历到末尾仍然找不到
  5. 查询节点,与删除节点类似
//需要先定义一个节点类然后才能实现链表
class HeroNode {
	private int no;//排序号
	private String name;//英雄名字
	private HeroNode next;//每个节点都应该包含有一个节点属性,用来指向下一个节点
	public HeroNode() {}
	public HeroNode(int no,String name) {
		this.no = no;
		this.name = name;
	}
	//这里省略属性的 Getter 、Setter方法和 toString方法
}
//定义链表类
class SingleLinkedList {
	private HeroNode head = new HeroNode();//头结点不存储数据,只是用来指向链表第一个节点
	private HeroNode temp;//中间变量,操作时需要用到
	
	//1.1 添加方法,直接添加到链表末尾
	public void addLast(HeroNode node) {
		temp = head;
		//先遍历到链表末尾
		while(temp.getNext() != null) {
			temp = temp.getNext();
		}
		//在链表末尾添加该节点(若链表为空也会跳到这一步)
		temp.setNext(node);
	}
	
	//1.2 添加方法,按升序方式添加
	public void addByOrder(HeroNode node) {
		temp = head;
		//若遍历到了链表末尾(包括空链表的情况)则结束循环
		while(temp.getNext() != null){
			if(temp.getNext().getNo() == node.getNo()) { //排序号在链表中已存在
				System.out.println("节点在链表中已存在!");
				return;
			}else if(temp.getNext().getNo() < node.getNo()) { //排序号比插入的节点小,继续向后遍历
				temp = temp.getNext();
			}else {
				temp.setNext(node);
				node.setNext(temp.getNext());
				return;
			}
		}
		//遍历到链表末尾
		temp.setNext(node);
	}

	//2.修改节点
	public void update(HeroNode newNode) {
		temp = head;
		while(temp.getNext() != null) {
			if(temp.getNext().getNo() == newNode.getNo()) {
				//找到了,对节点进行修改
				temp.getNext().getName() = newNode.getName();
				return; //修改完,退出方法
			} else {
				temp = temp.getNext();
			}
		}
		//遍历到链表末尾(包括空链表)仍然找不到
		System.out.println("要修改的节点不存在!");
	}

	//3.删除节点
	public boolean delete(int no) {
		temp = head;
		while(temp.getNext() != null){
			if(temp.getNext().getNo() == no) {
				temp.setNext(temp.getNext().getNext());
				return true;
			}else{
				temp = temp.getNext();}
		System.out.println("要删除的节点不存在!");
		return false;
	}
	
	//4.1 查询单个节点
	public HeroNode findByNo(int no) {
		temp = head;
		while(temp.getNext() != null){
			if(temp.getNext().getNo() == no) {
				return temp.getNext();
			}else{
				temp = temp.getNext();}
		System.out.println("节点不存在!");
		return null;
	}
	
	//4.2 遍历链表
	public void showList() {
		temp = head;
		if(temp.getNext() == null) {
			System.out.println("链表为空!");
			return;
		}
		while(temp.geNext() != null) {
			System.out.println(temp.getNext());
			temp = temp.getNext();
		}
	}
}
2.双向链表

在这里插入图片描述

//这里写和单链表有差异的几个方法,像修改,遍历是一样的实现就不写了
// 定义节点类,多出了一个前驱节点
class HeroNode {
	private int no;
	private String name;
	private HeroNode next;
	private HeroNode pre; //前驱节点
	//其他省略
}
//定义链表类
class DoubleLinkedList {
	private HeroNode head = new HeroNode();
	private HeroNode temp;

	//1.1 直接添加到末尾
	public void addLast(HeroNode node) {
		temp = head;
		//先遍历到链表末尾
		while(temp.getNext() != null) {
			temp = temp.getNext();
		}
		//在链表末尾添加该节点(若链表为空也会跳到这一步)
		temp.setNext(node);
		node.setPre(temp);  //多了一步设置前驱节点
	}
	
	//1.2 按升序方式添加
	public void addByOrder(HeroNode node) {
		temp = head;
		//若遍历到了链表末尾(包括空链表的情况)则结束循环
		while(temp.getNext() != null){
			if(temp.getNext().getNo() == node.getNo()) { //排序号在链表中已存在
				System.out.println("节点在链表中已存在!");
				return;
			}else if(temp.getNext().getNo() < node.getNo()) { //排序号比插入的节点小,继续向后遍历
				temp = temp.getNext();
			}else {
				temp.setNext(node);
				node.setPre(temp); //多了一步设置前驱节点
				node.setNext(temp.getNext());
				temp.getNext().setPre(node);//这里不用考虑temp.getNext()为空的情况,因为while循环的条件帮我们保证了temp.getNext()不为空
				return;
			}
		}
		//遍历到链表末尾
		temp.setNext(node);
		node.setPre(temp); //多了一步设置前驱节点
	}

	//删除
	public boolean delete(int no) {
		temp = head;
		while(temp.getNext() != null){
			if(temp.getNext().getNo() == no) {  //temp.getNext()是那个要删除的 节点
				temp.setNext(temp.getNext().getNext());
				if(temp.getNext().getNext() != null) {
					temp.getNext().getNext().setPre(temp);
				}
				return true;
			}else{
				temp = temp.getNext(); //找不到继续往后遍历}
		System.out.println("要删除的节点不存在!");
		return false;
	}
}
3.单向环形链表

1.应用场景
在这里插入图片描述
2.解决思路
在这里插入图片描述

//定义一个节点类
class Boy {
	private int no;
	private Boy next;
	public Boy(int no){
		this.no = no;
	}
	//其他省略
}
//定义环形链表类
class CircleSingleLinkedList {
	private Boy first = null;
	//1. 构造一个环形单向链表
	public void addBoy(int nums) { //nums 指的是要创建的环形链表长度
		private Boy curBoy = null; //辅助指针,用来帮助构造链表的
		//先进行数据校验
		if(nums < 1) {
			System.out.println("数据不正确!");
			return;
		}
		for(int i = 1; i <= nums; i++) {
			Boy boy = new Boy(i);
			if(i == 1) {  //如果是第一个孩子的情况
				first = boy;
				first.setNext(first);  //构成环形链表
				curBoy = first;   //让curBoy指向第一个孩子
			} else {   
				curBoy.setNext(boy);
				boy.setNext(first);
				curBoy = boy;  //让curBoy指向链表的最后一个孩子
			}
		}
	}
	//2.遍历环形链表
	public void showBoy() {
		if(first == null) {//先判断链表是否为空
			System.out.println("链表为空!");
			return;
		}
		private Boy curBoy = first;
		while(curBoy.getNext() != first) { // curBoy.getNext() == first,说明遍历完毕
			System.out.printf("小孩的编号 %d\n",curBoy.getNo());
			curBoy = curBoy.getNext();
		}
	}
	//3.约瑟夫问题解决
	public void countBoy(int startNo.int countNum,int nums) {
		//先进行数据验证
		if(startNo > nums || startNO < 1) {
			System.out.println("输入参数有误!");
			return;
		}
		addBoy(nums);//构造环形链表
		//构造辅助指针 helper
		private Boy helper = first;
		//让 helper 指向链表最后一个节点
		while(helper.getNext() != first) {
			helper = helper.getNext();
		}
		//小孩报数前,让 helper 和 first 同时向后移动 startNo - 1步
		for(int i = 0; i < startNo - 1; i++) {
			first = first.getNext();
			helper = helper.getNext();
		}
		//构造一个大循环,直到链表中只剩下一个first节点
		//在大循环里面,小孩开始报数,让 helper 和 first 同时向后移动 countNum - 1步,然后first所指向的小孩节点出圈
		while(helper != first) {
			for(int i = 0; i < countNum - 1; i++) {
			first = first.getNext();
			helper = helper.getNext();
			}
			System.out.printf("小孩%d出圈\n",first.getNo());
			//first所指向的小孩节点出圈
			first = first.getNext();
			helper.setNext(first);
		}
		System.out.printf("最后留在圈中的小孩编号为%d\n",first.getNo());
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值