数据结构-基础-逻辑结构-线性结构(线性表)-顺序表-链表-栈-队列-数组-哈希表-广义表-

文章目录

    • ==基础==
      • 概念
      • 时间复杂度
      • 空间复杂度
      • 常用算法时间、空间复杂度
    • ==一般线性表==
      • 区别(线性表/链表)
      • 线性表
      • 顺序表
      • 链表
        • 概述
        • 单链表
        • 双链表
        • 循环单链表
        • 循环双链表**
        • 静态链表**
    • ==受限线性表==
      • 区别(栈和队列)
      • 队列
    • ==扩展线性表==
      • 数组
      • 哈希表(散列表)
      • 广义表

基础

概念

结构:数据元素之间的关系;
数据结构:相互之间存在的一种或多种特定关系的数据元素的集合。包括三方面的内容:逻辑结构、存储结构(物理结构)和数据的运算。逻辑结构和存储结构密不可分,前者影响算法的设计,后者影响算法的实现。

逻辑结构:元素之间的逻辑关系。
数据的逻辑结构分为:线性结构和非线性结构。
数据结构-基础-逻辑结构-线性结构(线性表)-顺序表-链表-栈-队列-数组-哈希表-广义表-_第1张图片
集合:结构中的元素的关系只是“同属于一个集合”,别无其他关系;
线性表:数据元素之间只存在一对一的关系;
树形结构:数据元素之间存在一对多的关系;
图状结构:数据元素之间存在多对多的关系;

存储结构/物理结构:是数据结构在计算机中的表示;是逻辑结构用计算机语言的实现;包括数据元素的表示和关系的表示;
数据的存储结构分为:顺序存储、链式存储、索引存储和散列存储。
备注2022/8/15
在数据结构中,从存储上可以将它分为 顺序结构 和 非顺序结构。
顺序存储 = 存储的物理位置相邻;链式存储 = 存储的物理位置未必相邻,通过记录相邻元素的物理位置找到相邻元素;
索引存储 = 类似目录 ; 散列存储 = 通过关键字直接计算出元素的物理地址。

算法的五个特性: 有穷性、确定性、可行性、输入、输出

时间复杂度

概念
用来衡量算法随着问题规模增大,执行时间增长的快慢;

注意

  • 统计的是计算操作数量,而不是运行的绝对时间。
  • 体现的是计算操作量随数据大小N变化时的变化情况。
    假设:算法运行操作1/100次,则时间复杂度为常量级O(1)
    需要N/100N次,则时间复杂度为O(N)。

在这里插入图片描述

符号表示
时间复杂度有最差、平均、最佳三种情况。
案例:一个长度为N的数组,判断数组中是否有7,有返回true,没有返回false。

时间复杂度分析:

时间复杂度 分析
最佳 nums = {7,a,b,c…}
复杂度是1
最差 nums = {a,b,c,d…,n-1,7}
复杂度是n
平均 需要考虑输入数据的分布情况,计算所有数据情况下的平均时间复杂度

常见种类
在这里插入图片描述
从左到右,性能依次降低。
数据结构-基础-逻辑结构-线性结构(线性表)-顺序表-链表-栈-队列-数组-哈希表-广义表-_第2张图片

复杂度 说明
常数O(1) 运行次数与N大小呈常量关系,不随输入数据大小N的变化而变化
对数O(logN) 对数阶=每轮排除一半的情况,出现于二分法、分治等算法中
线性O(N) 循环运行次数与N大小呈线性关系,常出现于遍历法算法中
线性对数O(NlogN) 两层循环相互独立,第一层和第二层时间复杂度分别是O(logN) 和 O(N),总体时间复杂度为O(NlogN)
平方O(N2) 两层循环相互独立,都与N呈线性关系
指数O(2N) 指数阶常出现于递归
阶乘O(N!) 阶乘对应数学上常见的**“全排列”**

注意
二分法 时间复杂度是logN的说明
数据结构-基础-逻辑结构-线性结构(线性表)-顺序表-链表-栈-队列-数组-哈希表-广义表-_第3张图片
超时
即程序运行超过了规定的时间,一般OJ(Online judge)的超时时间是1s,也就是用例数据输入后最多要1s内得到结果;
如果写一个O(n)的算法,可以估算出来多大的时候算法的执行时间就会超过1s。
如果n的规模已经足够让O(n)的算法运行时间超过了1s,那么就该考虑log(n)的算法。

空间复杂度

概述
用来衡量算法随着问题规模增大,算法所需空间增长的快慢;

空间复杂度设计的空间类型介绍如下:

类型 介绍
输入空间 存储输入数据所需的空间大小
暂存空间 算法运行过程中,存储所有中间变量和对象等数据所需的空间大小
输出空间 算法运行返回时,存储输出数据所需的空间大小

一般情况下算法运行计算的空间复杂度,计算所需内容是:暂存空间、输出空间。

表示
空间复杂度统计算法在“最差情况”下使用的空间大小。
最差情况有两层含义:最差输入数据、算法运行中的最差运行点。

最差输入数据。当N<= 10 数组nums的长度恒定为10,空间复杂度为O(10) = O(1);当N>10,数组nums长度为N,空间复杂度为O(N)。则空间复杂度应为最差输入数据情况下的O(N)。

最差运行点。执行nums = [0] * 10是,算法仅使用O(1)大小的空间;执行nums = [0] * N 时,算法使用O(N)的空间;那么空间复杂度应为最差运行点的O(N)。

常见种类
在这里插入图片描述
数据结构-基础-逻辑结构-线性结构(线性表)-顺序表-链表-栈-队列-数组-哈希表-广义表-_第4张图片

复杂度 说明
常数O(1) 普通常量、变量、对象、元素数量与输入数据大小N无关的集合,皆使用常数大小的空间
对数O(logN) 对数阶常出现于分治算法的栈帧空间累计、数据类型转换等
快速排序,数字转化为字符串
线性O(N) 元素数量与N呈线性无关的任意类型集合
常见于以为数组、链表、哈希表等
平方O(N2) 元素数量与N呈平方关系
常见于矩阵,递归调用阶段
指数O(2N) 指数阶常见于二叉树、多叉树。

常用算法时间、空间复杂度

时间复杂度 和 空间复杂度 常用的表示是:o(1), o(n),o(n^2), o(logn), o(nlogn)
其中n表示数据量,详细解释如下:

符号 说明 常见算法
o(1) 表示数据量增加,时间、空间复杂度不受影响 哈希算法
o(n) 表示数据量几倍,耗时、内存空间也增加几倍 遍历算法
动态规划
o(n^2) 表示数据增加几倍,耗时、内存空间增加n的平方倍 冒泡排序
o(logn) 表示数据增加几倍,耗时、内存增加logn倍(即数据增加25倍,耗时、存储空间增加5倍) 二分查找
o(nlogn) 表示数据增加几倍,耗时、内存增加nlogn倍(即数据量增加25倍,耗时、存储空间增加25*5=125倍) 归并排序

一般线性表

区别(线性表/链表)

区别 线性表 链表
空间开辟 连续开辟一段空间,大小固定 一次只开辟一个节点的空间(单链表),空间大小是动态的
空间使用 不知道要存储多少元素,如果开辟内存太大,会造成空间浪费
知道存储元素所少,顺序表中每个元素的存储密度是1,完全不会有内存空间的浪费
不知道存储元素个数,存储一个元素开辟一个空间,浪费不那么严重
知道存储元素个数,每个结点都会有非数据项指针,就会造成内存浪费
编译器会为每个程序从内存上分配一段空间,给该程序使用,每次开辟空间时都是在随机的位置开辟的,使用单链表就会把该空间块弄的七零八碎,出现一些是用不到的碎片空间。
对CPU高速缓存的影响 连续开辟一段空间,可以一次把多个数据写入高速缓存,再写入主内存;线性表的 CPU高速缓存效率更高 单链表每需要存储一个数据就开辟一个空间,每个数据存储时都要单独写入高速缓存区,再写入主内存 ;单链表的CPU高速缓存效率更低
访问元素的时间复杂度 类似数组,通过下标访问元素,支持随机访问。时间复杂度O(1) 不支持随机访问,只能从头结点开始遍历整个链表。时间复杂度O(n)
随机位置插入、删除元素的时间复杂度 连续存储,插入、删除元素需要把它之后的元素全部后移或前移,时间开销很大 ; O(n) 插入或删除,只需要改变它的前驱元素及插入 或 删除元素的指向即可。O(1)

线性表

定义
具有相同数据类型的n个数据元素的有限序列(n大于0);

区分线性表、顺序表、链式表
线性表 = 逻辑结构,表示元素之间一对一的关系;
顺序表 和 链式表 = 存储结构。

顺序表

特点
表中元素的逻辑顺序与其物理顺序相同;
最主要特点是可以进行随机访问,即通过首地址和元素序号可以在O(1)时间内找到指定元素;
存储密度高,每个结点只存储数据元素;
逻辑上相邻的元素物理上也相邻,所以插入和删除需要移动大量的元素;

时间复杂度
线性表插入的时间复杂度是O(n)
线性表删除的时间复杂度是O(n)
线性表按值查找的时间复杂度是O(n)

链表

概述

增删快、查询慢的模型

  • 链表的元素称之为结点,结点中有:地址(结点的存储位置)、数据(①存储具体的数据②下一个结点的地址);
  • 头结点有:数据(①head②下一个结点的地址为^表示空)
  • 查询数据必须从头(head)结点开始查询
    数据结构-基础-逻辑结构-线性结构(线性表)-顺序表-链表-栈-队列-数组-哈希表-广义表-_第5张图片
    java中的链表节点
public class ListNode { // 用类来做一个节点
     int val; // 设置一个成员变量
     ListNode next; // 使用类来做指针,指向下一个
     ListNode(int x) { val = x; } 
     // 带参构造方法,由于两个参数的名字不同,所以不同使用this.参数名 表示当前对象的成员
 }

java中没有指针的概念,只有引用数据类型,所以使用一个节点来充当指针。

单链表

概念
单链表 = 线性表的链式存储,是通过一组任意的存储单元来存储线性表中的数据元素。
为了建立数据与元素之间的线性关系,每个链表节点包含两部分:data和next。data为数据域,存放数据元素;next为指针域,存放其后继节点的地址。

头结点:
在单链表的第一个节点之前附加一个节点,称为头结点。
头结点的数据域可以不设任何信息,也可以记录表长等相关信息。
头结点的指针域指向线性表的第一个元素节点。

时间复杂度O(n)
头插法建立单链表每个节点插入的时间为O(1),设单链表元素为n,则总的时间复杂度为O(n)
尾插发建立单链表每个节点插入的时间为O(1),设单链表元素为n,则总的时间复杂度为O(n)
按序号查找节点值的时间复杂度为O(n)
按值查找表节点的时间复杂度为O(n)
插入节点的时间复杂度为O(n),若是在给定的节点后面插入新节点,则时间复杂度仅为O(1)
删除节点的时间复杂度为O(n)
求表长的时间复杂度为O(n)

双链表

概念
结点结构包括三个部分:data数据、next指向后一个元素指针、pre指向前一个元素指针;

性质
不带头结点、带尾指针tail、带头指针head;
tail的next = null、head的pre = null;

操作
链表主要的操作还是增删。增删需要考虑是否带头节点,头插、尾插、中间插等问题。

// 节点
class node<T> {
    T data;
	node<T> pre;
	node<T> next;

	public node() {
	}

	public node(T data) {
		this.data = data;
	}
}
// 链表
public class doubleList<T> {
    private node<T> head;// 头节点
	private node<T> tail;// 尾节点
	private int length;
    //各种方法	
}

双链表初始化
head始终指向第一个真实有效的数据,tail也是这样。

public doubleList(){
	head = null;
	tail = head;
	length = 0;
}

增加=空表

// 创建一个节点
node<T> teamNode = new node(data);
if (isEmpty()) { // 对于空表而言,head和tail都需要指向真实数据,直接赋值即可。
	head = teamNode;
	tail = teamNode;	
}

增加 = 头插

node<T> teamNode = new node(data);//创建要插入的新节点
head.pre = teamNode; // 头结点的pre = 新节点
teamNode.next = head; // 新节点的next = 头结点
teamNode.pre = null; // 新节点的pre = null
head = teamNode; // 重新定义头结点

增加 = 尾插

node<T> teamNode = new node(data); //创建要插入的新节点
tail.next = teamNdoe; // 尾结点的next = 新节点
teamNode.pre = tail; // 新节点的pre = 尾结点
teamNode.next = null; // 新节点的next = null
tail = teamNode; // 重新定义尾结点

增加 = 中间插入

node<T> teamNode = new node(data); // 创建要插入的新节点
node<T> preNode; // 插入位置
teamNode.pre = preNode; // 新节点的pre = preNode
teamNode.next = preNode.next; // 新节点的next = preNode.next
preNode.next = teamNode; 

删除 = 单点删除

// 链表中只有一个元素 无论头插还是尾插,直接将链表初始化就好
if(length = 1){
	head = null;
	tail = head;
	length = 0;
}

删除 = 头删

node<T> newHead = head.next; // 获取头结点的next元素
newHead.pre = null; // 将该节点的pre设置为null
head = newHead; // 重新定义头结点

删除 = 尾删

node<T> newTail = tail.pre;  // 获取尾结点的pre元素
newTail.next = null; // 新尾结点的next = null
tail = newTail; // 重新定义尾结点

删除 = 中间

node<T> deleteNode ; // 要删除的结点
noet<T> preNode = deleteNode.pre; // 要删节点的前节点
node<T> nextNode = deleteNode.next; // 要删节点的后节点
preNode.next = nextNode; // 前节点的next = 后节点
nextNode.pre = preNode; // 后节点的pre = 前节点

//简化
deleteNode.next.pre = deleteNode.pre;
deleteNode.pre.next = deleteNode.next;
循环单链表

概念
与单链表区别:

区别 单链表 循环单链表
结构 最后一个元素的next 是null 最后一个元素的next不是null,而是指向head节点
结束标志 节点的next = null 没有,非要找到结束标志的话就是元素的next节点指向head
遍历方式 每次遍历都得从head节点开始 可以从任意节点开始
操作方式 对head节点较为关注 不关注head节点,带来极大便利
应用场景 传统队列特性的数据处理(排队候车) 具有环状特性的数据处理(约瑟夫问题)

特点

  • 循环链表不关注结点顺序,那在新增结点时,我们就不必寻找最后一个结点,而是直接跟在头结点后;
  • 因为循环链表的循环特性,我们无需关注边界值,无论在什么位置,都使用一样的插入流程。
  • 循环链表不关注头结点的位置,定义head节点通过场景需要,最方便后续操作的节点为头结点;
    (存在问题):会导致A线程(头结点在a)正在工作时,B线程突然把头结点的位置(改为了b)更改了,此时A线程的操作必将出错。

循环单链表基本结构

public class CircularLinkList<E> {
    public static Random RANDOM = new Random();
	public MyNode<E> head; // 头结点
    public int size = 0; // 链表长度
    public String outOfBoundsMsg(int index) {
        return "Index: " + index + ", Size: " + size;
    }
    // 节点定义
    class MyNode<E> {
        E data; // 数据
        MyNode<E> next; //下一个节点
        public MyNode(E data) {
            this.data = data;
        }
    }
 }

新增 插入

public MyNode<E> add(E e){ // 参数表示data数据 返回插入的节点
	MyNode<E> newNode = new MyNode<E>(e);
	// 空链表插入
	if(isEmpty()){
		head. = newNode;
		newNode.next = head;
	}else{
		newNode.next = head.next;
		head.next = newNode;
	}
	size++;
	return newNode;
}

新增 在指定节点后插入

public MyNode<E> add(MyNode<E> node,E e){
	MyNOde<E> newNode = new MyNode<E>(e);

	if(node == null){
		throw new IllegalArgumentException("Base node can not be null.");
	}else{
		newNode.next = node.next;
		node.next = newNode;
	}
	size++;
	return newNode;	
}

删除

// 删除某结点往后的第index个结点,之后将head移动到被删结点的后一个结点
public MyNode<E> remove(MyNode<E> node,int index){
	if(isEmpty()){
		throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
	}else{
		MyNode<E> del; // 要删除的结点
		MyNode<E> curr = node; // 当前结点
		int count = 1; // 统计个数
		// while(count < index + size -1){//这个内容没有看懂?
		while(count < index){// 个人理解应该是这个。
			count++;
			cur = cur.next;
		}
		del = cur.next;
		cur.next = del.next;
		// 重置头结点
		head = cur.next;
		size--;
		return del;
}

数据结构-基础-逻辑结构-线性结构(线性表)-顺序表-链表-栈-队列-数组-哈希表-广义表-_第6张图片
随机获取

public MyNode<E> get(){
	int index = Random.nextInt(size); // 产生一个0到指定size之间的数。
	MyNode<E> cur = head;
	int count = 0 ; 
	while(count < index){
		cur = cur.next;
		count ++;
	}
	return cur;
}

遍历
结束条件:从头结点开始 到头结点结束。

public String toString(){
	StringBuilder str = new StringBuilder();
	if(isEmpty()){
		return "";
	}
	str.append(head.data.toString());
	str.append(" ");
	MyNode<E> cur = head.next;
	while(cur != head){
		str.append(cur.data.toString());
		str.append(" ");
		cur = cur.next;
	}
	retrun str.toString();
}
循环双链表**
静态链表**

受限线性表

区别(栈和队列)

区别 队列
操作名称 进栈、出栈 入队、出队
操作限定 都在栈顶 入队在队头、出队在队尾
操作规则 后进先出 先进先出
遍历速度 只能从栈顶操作,需遍历整个栈才可取出,需要开辟临时空间,保证数据一致性 遍历速度快,基于地址指针,可以从头部或尾部进行遍历,不能同时遍历,无需开辟空间
应用场景 括号问题求解、表达式转换和求值、函数调用、递归、深度优先搜索 计算机系统中各种资源的管理、消息缓冲器和广度优先搜索算法

  • 是一个重要的数据结构,具有先进先出的特点;
    public class stack< E > extends Vector< E > 继承自Vector数组;说明栈底层也是一个数组
  • 栈stack具有先进后出的特点,出栈和入栈都是在栈顶操作的;
  • 栈只有一端,有栈顶和栈底,只是栈顶开口,数据进栈出栈规律是:先进栈的元素最后出栈
  • 数据进入栈模型的过程称为:压/进栈,位于栈底的元素称为栈底元素,位于栈顶的元素称为栈顶元素
  • 数据离开栈模型的过程称为:弹/出栈

常用方法

方法 说明
stack() 创建一个空堆栈
boolean empty() 测试此堆栈是否为空
E peek() 查看此堆栈顶部的对象,而不从堆栈中删除它
E pop() 删除此堆栈顶部的对象,并将该对象作为此函数的值返回
E push( E item) 将元素推送到此堆栈的顶部
int search(object o) 返回一个对象在此堆栈上的基于1的位置
extends Vector
public int size()
返回此向量中的组件数

stack如何扩容
栈构造方法只有一个,且stack继承自Vector,父类默认容量是10,它扩容的是push方法实现的;
Stack直接调用了Vector类的addElement(E item)方法;

队列

先进先出模型

  • 有两端,分别是前端、后端
  • 数据从后端进入队列模型的过程称为:入队
  • 数据从前端离开队列模型的过程称为:出队

扩展线性表

数组

概述
数组:用于存储固定大小的同类型元素;元素是不可再分的,也可以用是数组(二维数组)。

特点
查询快、增删慢的模型

  • 查询数据通过索引定位,查询任意数据耗时相同,查询效率高
  • 删除数据时,要将原始数据删除,同时后面每个数据前移,删除效率低
  • 添加数据时,添加位置后的每个数据后移,再添加元素,添加效率极低

哈希表(散列表)

概念
散列表/哈希表(hash table),是根据关键码值(Key value)而直接进行访问的数据结构。
表示了关键码值key和记录的映射关系。

查找通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。
这个映射函数叫做散列函数,存放记录的数组叫做散列表。

广义表

概念
广义表,又称列表,也是一种线性存储结构。
广义表中既可以存储不可再分的元素(原子),也可以存储广义表(子表)。

广义表存储数据常见格式
A = ():A 表示一个广义表,只不过表是空的。
B = (e):广义表 B 中只有一个原子 e。
C = (a,(b,c,d)) :广义表 C 中有两个元素,原子 a 和子表 (b,c,d)。
D = (A,B,C):广义表 D 中存有 3 个子表,分别是A、B和C。这种表示方式等同于 D = ((),(e),(b,c,d)) 。
E = (a,E):广义表 E 中有两个元素,原子 a 和它本身。这是一个递归广义表,等同于:E = (a,(a,(a,…)))。
A = () 和 A = (()) 是不一样的。前者是空表,而后者是包含一个子表的广义表,只不过这个子表是空表。

广义表 表头 表尾
当广义表不是空表时,称第一个数据(原子或子表)为"表头",剩下的数据构成的新广义表为"表尾"。
除非广义表为空表,否则广义表一定具有表头和表尾,且广义表的表尾一定是一个广义表。
案例:广义表 LS = {1} 中,表头为原子 1 ,但由于广义表中无表尾元素,因此该表的表尾是一个空表,用 {} 表示。

你可能感兴趣的:(2023个人面试备战,数据结构,java,算法)