数据结构与算法weeks01

       从这周开始学习数据结构与算法
       首先老规矩先分享一下视频来源,又又又是尚硅谷的一位老师。废话还能是谁?
在这里插入图片描述

b站视频连接:https://www.bilibili.com/video/BV1E4411H73v
在这里插入图片描述OK,话不多说开整



一、数据结构与算法概述

1.数据结构和算法的关系

一堆废话:

       1) 数据结构(structure)是一门研究组织数据方式的学科:学好数据结构→漂亮并效率高的代码。
       2) 要学习好数据结构:用程序去实现解决生活中遇到的问题。
       3) 程序 = 数据结构 + 算法
       4) 数据结构是算法的基础, 换言之,想要学好算法,需要把数据结构学到位。

上面说了一堆,其实就是下图的关系–>

在这里插入图片描述


2.数据结构

数据结构包括:线性结构和非线性结构。

2.1线性结构

       1)特点:数据元素之间存在一对一的线性关系
       2)两种不同的存储结构:顺序存储结构和链式存储结构。
       3)链式存储的线性表称为链表
       4)线性结构常见的有:数组、队列、链表和栈。

2.2非线性结构

       非线性结构包括:二维数组,多维数组,广义表,树结构,图结构 ,AND 没了。。。。看着简单可能很难。


二、稀疏数组和队列

他来了她来了,数据结构的学习就从稀疏数组开始吧~~
在这里插入图片描述

1.稀疏数组

1.1实际需求

       老师上课讲的是五子棋的例子,棋盘实际上就是一个二维数组,很多值默认都为0,因此记录了很多没有意义的值。
棋盘:

在这里插入图片描述对应的原始数组:
在这里插入图片描述
       因此我们需要将原始数组转换为稀疏数组,当然也就需要将稀疏数组转变为原始数组。

1.2思路分析及代码

1.2.1原始数组—>稀疏数组

1)思路分析
       稀疏数组需要存储:
       ①原始数组的行数和列数
       ②遍历原始数组,得到非零值以及其坐标

2)上述原始数组的稀疏数组:
在这里插入图片描述其中:稀疏数组共有3行3列,
第一行:表示原始数组大小为11×11,val表示原始数组中非零值的个数
第二行和第三行:表示非零值的坐标以及其对应的值

3)代码如下:

//1.先遍历二维数组得到非0数据的个数
int sum = 0;
for(int i = 0;i < 11;i++){
	for(int j = 0;j < 11;j++){
		if(chessArr1[i][j] != 0){
			sum++;
		}
	}
}
//2.创建对应的稀疏数组
int sparseArr[][] = new int[sum + 1][3];
//给稀疏数组赋值
sparseArr[0][0] = 11;
sparseArr[0][1] = 11;
sparseArr[0][2] = sum;
//遍历二维数组,将非0的值存放到sparseArr中
int count = 0;//count 用于记录第几个非0数据
for(int i = 0;i < 11;i++){
	for(int j = 0;j < 11;j++){
		if(chessArr1[i][j] != 0){
			count++;
			sparseArr[count][0] = i;
			sparseArr[count][1] = j;
			sparseArr[count][2] = chessArr1[i][j];
		}
	}
}
1.2.2稀疏数组—>原始数组

经过上面的分析,稀疏数组转原始数组就很简单了。
直接上代码:

//1. 先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组
int chessArr2[][] = new int[sparseArr[0][0]][sparseArr[0][1]];
//2.在读取稀疏数组后几行的数据(从第二行开始),并赋给 原始的二维数组 即可.
for(int i = 1;i < sparseArr.length;i++){
	chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
}

1.3附加内容

       老师上课留了问题,即可以将稀疏数组保存到本地,就用到了IO流的知识。经过我亲自的试验发现用对象流比较简单。
代码如下(示例):

//将稀疏数组保存到本地
oos = new ObjectOutputStream(new FileOutputStream(filePath));
oos.writeObject(sparseArr);
oos.flush();//刷新操作

//使用对象流读取文件
ois = new ObjectInputStream(new FileInputStream(filePath));
int[][] sparseArr = (int[][]) ois.readObject();

2.队列

       队列其实就是排队,遵循先入先出的原则,这一点上与先入后出的栈格格不入。

2.1数组模拟队列

2.1.1思路分析

1)几个值的说明
maxSize:数组的最大容量
front:指向队列头部,默认值为-1
rear:指向队列尾部,默认值为-1
在这里插入图片描述2)两种情况
队列空:rear = front
队列满:rear = maxSize - 1

2.1.2代码
//添加数据到队列
public void addQueue(int n){
	//判断队列是否满
	if(isFull()){
		System.out.println("队列满,不能加入数据~");
		return;
	}
	rear++;//让rear后移
	arr[rear] = n;	
}
//获取队列的数据,出队列
public int getQueue(){
	//判断队列是否空
	if(isEmpty()){
		//通过抛出异常
		throw new RuntimeException("队列空,不能取数据");
	}
	front++;//front后移
	return arr[front];
}
//显示队列的所数据
public void showQueue(){
	//遍历
	if(isEmpty()){
		System.out.println("队列为空,没数据~");
		return;
	}
	for (int i = 0; i < arr.length; i++) {
	System.out.printf("arr[%d]=%d\n",i,arr[i]);
	}
}

2.2数组模拟环形队列

上述队列存在着只能使用一次的缺点,即不能复用,于是出现了环形数组模拟队列。

2.2.1思路分析

1)几个值的说明
maxSize:数组的最大容量
front:指向队列头部,默认值为0,其实指向队列的第一个元素
rear:指向队列尾部,默认值为0,指向队列最后一个元素的下一个位置
有效数据个数:(rear + maxSize - front) % maxSize
区别:
主要体现在默认值的改变
front++和rear++是在出队列或入队列前进行还是之后进行

//数组模拟队列:
rear++;//让rear后移
arr[rear] = n;	
//数组模拟环形队列
arr[rear] = n;
//将rear后移,这里必须考虑去摸
rear = (rear + 1) % maxSize;

在这里插入图片描述

2)两种情况
队列空:rear = front
队列满:(rear + 1) % maxSize = front

2.2.2代码
//添加数据到队列
public void addQueue(int n){
	//判断队列是否满
	if(isFull()){
		System.out.println("队列满,不能加入数据~");
		return;
	}
	//直接将数据加入
	arr[rear] = n;
	//将rear后移,这里必须考虑去摸
	rear = (rear + 1) % maxSize;
}
	
//获取队列的数据,出队列
public int getQueue(){
	//判断队列是否空
	if(isEmpty()){
		//通过抛出异常
		throw new RuntimeException("队列空,不能取数据");
	}
	//这里需要分析出front是指向队列的第一个元素
	//1.先把front对应的值保留到一个临时变量
	//2.将front后移,考虑取模
	//3.将临时保存的变量返回
	int value = arr[front];
	front = (front + 1) % maxSize;
	return value;
}

//显示队列的所数据
public void showQueue(){
	//遍历
	if(isEmpty()){
		System.out.println("队列为空,没数据~");
		return;
	}
	//思路:从front开始遍历,遍历多少个元素
	for(int i = front;i < front + size();i++){
		System.out.printf("arr[%d]=%d\n",i % maxSize,arr[i % maxSize]);
	}
}

三、链表

1)链表是有序的列表
2)链表是以节点的方式来存储,是链式存储
2)每个节点包含 data 域, next 域:指向下一个节点
3)链表的各个节点不一定是连续存储
4)链表分带头节点的和没有头节点的
5)头节点不存放具体的数据,只是表示链表的表头

在这里插入图片描述

1链表的应用实例

使用带头节点的单向链表实现 –水浒英雄(HeroNode)排行榜管理,完成对英雄人物的增删改查操作:
       添加数据:
       方式一:直接添加到链表的尾部

while(true){
	if(temp.next == null){
		break;
	}
	temp = temp.next;
}
temp.next = heroNode;

       方式二:根据排名添加

if(temp.next.no > heroNode.no){
	heroNode.next = temp.next;
	temp.next = heroNode;
}

       修改数据:根据no编号来修改

if(temp.no == newHeroNode.no){
	temp.name = newHeroNode.name;
	temp.nickname = newHeroNode.nickname;
}

       删除数据:找到要删除节点的上一节点,然后将这个节点的next值指向要删除节点的next。

temp.next = temp.next.next;

2.单链表面试题

1)查找单链表中的倒数第k个结点
       先得到链表中数据的个数
       然后遍历数组到指定的索引处即可

public static HeroNode findLastIndexNode(HeroNode head,int index){
	//判断如果链表为空,返回null
	if(head.next == null){
		return null;//没找到
	}
	//第一次遍历得到链表的长度(节点个数)
	int size = getLength(head);
	//第二次遍历size - index位置,就是我们倒数的第k个节点
	//先做一个index的效验
	if(index <= 0 || index > size){
		return null;
	}
	//定义辅助变量,for循环定位到倒数的index
	HeroNode cur = head.next;//3 //3 - 1 = 2
	for(int i = 0;i < size - index;i++){
		cur = cur.next;
	}
	return cur;
}

2)单链表的反转
       先新new一个新的头节点,然后依次将数据插入,关键在于数据每次都插入到新的头节点的下一个节点。

public static void reverseList(HeroNode head){
	//如果当前链表为空,或者只一个节点,无需反转,直接返回
	if(head.next == null || head.next.next == null){
		return;
	}
	//定义一个辅助的指针(变量),帮助我们遍历原来的链表
	HeroNode cur = head.next;
	HeroNode next = null;//指向当前节点[cur]的下一个节点
	HeroNode reverseHead = new HeroNode(0,"","");
	//遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端
	while(cur != null){
		next = cur.next;//暂时保存当前节点的下一个节点,因为后面需要使用
		cur.next = reverseHead.next;//将cur的下一个节点指向新的链表的最前端
		reverseHead.next = cur;//将cur连接到新的链表中
		cur = next;//让cur后移
	}
	//将head.next指向reverseHead.next,实现单链表的反转
	head.next = reverseHead.next;
}

3.双向链表

       1)双向链表与单向链表在定义上的区别在于:
       当前节点既可以通过pre指向前一个节点,又可以通过next指向下一个
       2)完成对英雄人物的增删改查操作:
       添加数据:
       方式一:直接添加到链表的尾部

//先找到链表的尾部
while(true){
	if(temp.next == null){
		break;
	}
	temp = temp.next;
}
//然后
temp.next = heroNode;
heroNode.pre = temp;

       方式二:根据排名添加

//与单向链表的区别是:不需要借助上一个节点找到
if(temp.no > heroNode.no){
	heroNode.next = temp;
	heroNode.pre = temp.pre;
	temp.pre.next = heroNode;
	temp.pre = heroNode;
}

       修改数据:根据no编号来修改

//与单链表一致
if(temp.no == newHeroNode.no){
	temp.name = newHeroNode.name;
	temp.nickname = newHeroNode.nickname;
}

       删除数据:找到要删除节点

//与单链表不同的是,可以直接定位到待删除的节点
temp.pre.next = temp.next;
//如果是最后一个节点,就不要执行下面这句话,否则出现空指针
if(temp.next != null){
	temp.next.pre = temp.pre;				
}

4.单向环形链表

4.1单向环形链表:

在这里插入图片描述1)思路分析
       先创建第一个节点first,并且first.next = first;
       然后继续创建剩下的节点,加入到已有的环形链表中即可
2)代码

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;
	}
}

4.2Josephu(约瑟夫、约瑟夫环)问题

       设编号为1,2,… n的n个人围坐一圈,约定编号为k的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
1)思路分析
创建一个helper指向链表最后的节点,即helper.next = first;
然后helper与first同时移动。

在这里插入图片描述

2)代码

public void countBoy(int startNo,int countNum,int nums){
	//创建一个辅助指针,帮助小孩出圈
	Boy helper = first;
	//需要创建一个辅助指针(变量)helper,事先应该指向环形链表的最后这个节点
	while(true){
		if(helper.getNext() == first){//说明helper指向最后小孩节点
			break;
		}
		helper = helper.getNext();
	}
	//小孩报数前,先让 first和 helper移动 k-1次
	for(int i = 0;i < startNo - 1;i++){
		first = first.getNext();
		helper = helper.getNext();
	}
	//当小孩报数时,让first和helper指针同时移动m-1此,然后出圈
	//这里是一个循环操作,直到圈中只有一个节点
	while(true){
		if(helper == first){//说明圈中只一个节点
			break;
		}
		//让first和helper指针同时的移动countNum - 1
		for(int i = 0;i < countNum - 1;i++){
			first = first.getNext();
			helper = helper.getNext();
		}
		//这是first指向的节点,就是要出圈的节点
		System.out.printf("小孩%d出圈\n",first.getNo());
		//这时将first指向的小孩节点出圈
		first = first.getNext();
		helper.setNext(first);//
	}
	System.out.printf("最后留在圈中的小孩编号%d\n",helper.getNo());
}

总结

To be continued~~,栈,递归,排序
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值