数据结构与算法是有一定难度的,大家学习的时候要多练,一时不懂没关系要多多练习,越练越能明白其中的意义;
数据结构
1,逻辑结构
这方面我先讲概念,后面会有例子给大家深入了解
逻辑结构分为:
线性结构:线性表,栈,队列,串
非线性就结构:树,图
存储结构分为:
顺序存储结构:连续依次存储的单元存储元素,数据元素之间的存储关系来表示关系
链表存储结构:
用一组任意存储的单元存储元素,数据元素之间的逻辑关系用指针来表示
索引存储结构:
- 在存储信息的时候,建立一个附加的索引表
- 索引表每一页为一个索引项,也就是地址,能通过关键字来标识结点的数据
- 稠密索引:每个结点都有一个索引项在表中关联
- 稀疏索引:一个表只有一个索引关联
散列存储:根据结点的关键字直接计算出该结点的存储地址
数据类型:
-
以java为例子
基本数据类型就是byte,short,int,long,float,double,char,boolean等,
构造数据类型就是数组,集合,枚举等;
空类型void,跟c语言比差了指针,结构,共用体;
基本的数据类型可以实现数组,集合字符等,但是不能表示栈,队列,树,图等; -
数据类型:就的一组性质相同的值的集合以及定义这个集合的一组值的总称;
作用就是约束变量或者常量的取值范围,约束变量或者常量的操作
2.算法
算法所耗费的时间:该算法每条语句的频度之和。
1. 时间复杂度:
只比较函数之间的数量级,如函数a=5n^2,而函数b=10n;那么假设那无穷大时,函数的增长率是相同的,所以a函数比较复杂;
求算方法:有个关于时间复杂度的多项式,不考虑低次幂和高次项的系数,只考虑贡献最大的项式的高次幂;
伪代码:
int a = 0;
for(int i=1;i<n;i++){//循环n次最后判断一次是否符合循环条件+1次所以运行了n+1次
for (int j=(i+1);j<n;j++){//外层执行n次,内层执行n-1次判断循环条件加一次为n次;这里执行n*(n)次
a++;
}
}
2.空间复杂度:
将算法存储所需要的空间表示为关于n的函数;比如:数组a中有n个逆序存储到原数组
伪代码:
for(int i=0;i<n/2;i++){
temp = a[i];
//将数组a数组的开头和结尾互换中间也一次互换需要换n/2次,只需要一个temp作为中间的存在空间顾空间复杂度为1
a[i] = a[n-i-1];
a[n-i-1] = temp
}
伪代码:
for(int i=0;i<n;i++){
temp[i] = a[n-i-1];
//将数组a从末尾往头依次取出元素,依次放到temp数组的头到尾;一样也完成数组的逆序存储,只不过是空间上必须要有一个跟数组a一样的空间的数组temp所以空间复杂度为n;
for(int i=0;i<n;i++){
a[i] = temp[i];
}
}
3.稀疏数组:
将二维数组中无用的数据压缩,成一个3列n行的的数组;
下标 行 列 值
[0] 原数组几行 原数组几列 有用的值有几个
[1]
[2]
[3]
[4]
[5]
[1] - [5]是数组下标,中间区域记录的是具体数据的所在的行,所在的列,具体数据
//新建一个二维数组
int[][] chessArr = new int[11][11];
chessArr[1][2] = 11;
chessArr[3][5] = 23;
chessArr[4][8] = 45;
//输出二维数组
System.out.println("这是一个二维数组:");
for (int[] row : chessArr){
for (int data : row){
System.out.printf("%d\t",data);
}
System.out.println();
}
//遍历数组取出有用的数据;
int sum = 0;
for (int i = 0;i < chessArr.length;i++) {
for (int j = 0;j< chessArr.length;j++) {
if (chessArr[i][j]!=0) {
sum++;
}
}
}
System.out.println("sum 值:"+sum);
//定义一个稀疏数组来存放这些数据
int [][] sparseArr = new int[sum+1][3];
sparseArr[0][0] = 11;
sparseArr[0][1] = 11;
sparseArr[0][2] = sum;
int count = 0;
//因为需要用chessArr的i和j所以要对原来的数组遍历取出数据
for (int i = 1;i<11;i++) {
for (int j = 1;j<11;j++) {
if (chessArr[i][j]!=0) {
//因为稀疏数组的第一行是记录原数组的大小和有用数据的多少
//在前面定义过了所以使用一个计数器count来表示从第一行以外的数据进行装配
count++;
sparseArr[count][0] = i;
sparseArr[count][1] = j;
sparseArr[count][2] = chessArr[i][j];
}
}
}
//输出稀疏数组
for (int[] row : sparseArr){
for (int data : row){
System.out.printf("%d\t",data);
}
System.out.println();
}
//将稀疏数组转为二维数组
int[][] chessArr2 = new int[sparseArr[0][0]][sparseArr[0][1]];
//提取稀疏数组中的数据并装配到这个二维数组中
for (int i = 1;i<sparseArr.length;i++){
//从第二行开始遍历,如果从第行开始遍历会越界;因为前面定义数组就决定了长度
chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
}
//遍历二维数组输出与最开始的二维数组看下有没有差异
System.out.println();
for (int[] row :chessArr2){
for (int data : row){
System.out.printf("%d\t",data);
}
System.out.println();
}
4.队列
- 什么是队列;是一种先入先出的数据结构;
- 头部指针first指向队列头部的前一个数据,尾部指针last指向队列的尾部(最后一个数据);
- 添加数据的时候影响尾指针,而取出数据的时候影响的是front指针
1. 数组模拟队列思路分析:
- 首先要创建一个队列,队列里面必须要有3个属性头指针,尾指针,最大容量;因为是数组模拟所以还要有个数组来存放数据。
- 创建完了,要有判断队列什么时候为空,什么时候满,不然没办法往里面添加数据和去数据
- 队列为空时,队列front和rear都为-1,那么我们可以推出当队列front==rear时队列为空
- 队列为满,添加一个数据满了,rear当尾指针指向最大容量时队列就满了那么rear==MaxSize-1
- 往队列中添加数据,添加影响尾指针,头部不影响,那么忽略头部,添加一个元素队列rear+1,向后移动,这时候有个问题,数组是不能动态扩容,所以要么重写ArrayList实现动态数组,要么实现环形队列这个以后会发后续的博客,到时候再讲,先给大家一个概念。
- 从队列取数据,影响的是尾部指针,取一个数据时,front+1
2.具体实现代码
public static void main(String[] args) {
//测试
//创建一个队列
ArrayQueue queue = new ArrayQueue(3);
char key = ' ';
//接受用户输入
Scanner scanner = new Scanner(System.in);
boolean loop = true;
while (loop){
System.out.println("s(show):显示队列");
System.out.println("e(exit):退出程序");
System.out.println("a(add):添加数据到队列");
System.out.println("g(get):从队列取数据");
System.out.println("h(head):查看队列头部");
key = scanner.next().charAt(0);
//接受一个字符
switch (key){
case 's' :
queue.showQueue();
break;
case 'a' :
System.out.println("输出一个数");
int value = scanner.nextInt();
queue.addQueue(value);
break;
case 'g' :
try {
int res = queue.getQueue();
System.out.printf("取出数据%d\n",res);
}catch (Exception e){
System.out.println(e.getMessage());
}
break;
case 'h' :
try {
int res = queue.headQueue();
System.out.printf("取出数据%d\n",res);
}catch (Exception e){
System.out.println(e.getMessage());
}
break;
case 'e' :
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出");
}
}
class ArrayQueue{
//创建队列需要的属性
private int maxSize;
private int front;
private int rear;
private int[] arr;
//创建队列的构造器(初始化时就能直接生效)
public ArrayQueue(int arrMaxSize){
//这里是用临时变量的方式传递值,你们也可以用this
maxSize = arrMaxSize;
arr = new int[maxSize];
//用来存放数据
front = -1;
//指向队列头的前一个位置
rear = -1;
//指向队列尾部(最后一个数据)
}
//判断队列已满
public boolean isFull(){
return rear == maxSize-1;
}
//判断队列为空
public boolean isEmpty(){
return rear == front;
}
//添加数据到队列
public void addQueue(int n){
if (isFull()){
System.out.println("队列已经满了,不能添加数据");
return;
}
rear++;
arr[rear] = n;
}
//获取队列数据(出队列)
public int getQueue(){
if (isEmpty()){
//通过异常来返回信息
throw new RuntimeException("队列为空不能去数据");
//这里要注意不加return,因为throw本身会马上返回,导致代码return
}
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]);
}
}
public int headQueue(){
if (isEmpty()){
throw new RuntimeException("队列空的,没有数据");
}
return arr[front+1];
//因为front指向的是队列头的前一个数据
}