数据结构学习

笔记连接

柠檬博客面试笔记

柠檬笔记
柠檬-数据结构(hsp)
简书-hsp目录
力扣hot100 分类 github
力扣笔记一些
多路归并求解问题

算法博主

力扣专栏
github链接
labuladong 公众号笔记
BAT霜神Leetcode刷题笔记

1.链表

1.1 双指针问题 倒数第k个

思路

2.二叉树

二叉树 前、中、后 遍历

2.1 二叉树非递归 前序列

根节点入栈
输出根节点,再入栈右子节点。
再入栈左子节点。

	/**
 * @brief 先序遍历(非递归版本)
 *
 * @param root 根节点
 */
void preOrderIterative(treeNode *root)
{
    if (NULL == root) return;  // 空树

    stack<treeNode *> nodeStack;  // 创建一个栈保存节点
    nodeStack.push(root);         // 根节点进栈

    while (!nodeStack.empty())  // 栈非空时迭代处理
    {
        treeNode *node = nodeStack.top();  // 保存栈顶节点
        printf("%d ", node->data);         // 访问节点数据
        nodeStack.pop();                   // 栈顶节点出栈

        // 子节点入栈
        if (node->right) nodeStack.push(node->right);
        if (node->left) nodeStack.push(node->left);
    }
}

2.2 二叉树非递归 中序

① 先沿着根的左孩子依次入栈,直到左孩子为空为止。
② 当前栈顶元素出栈,输出当前元素,(若右孩子为空,则继续执行,输出当前栈顶元素)
③ 否者执行① 右孩子节点不为空
口诀 :入栈向左一直走,出栈 访问右子树。
入栈 push(cur);
向左 cur=cue.left;
一直走 null
出栈 temp=pop();
访问 print(temp)
右子树 cur=temp.right;
while条件
while( p!=null || !s.empty())
需要两个条件时因为
在23
A 出栈 A的右子节点为C ,此时栈未空。若结束循环 C未访问。

 public List<Integer> inorderTraversal(TreeNode root) {
            Stack<TreeNode> stack=new Stack<>();
            TreeNode cur=root;
            List<Integer> list=new ArrayList<>();
            //注意 需要当前节点为空且栈为空才结束循环
            while(cur!=null || !stack.empty()){
                if(cur!=null){
                    //入栈 
                    stack.push(cur);
                    // 向左
                    cur=cur.left;
                }else{
                    // 出栈
                    TreeNode node=stack.pop();
                    // 输出
                    list.add(node.val);
                    // 向右子树
                    cur=node.right;
                }
            }
            return list;
    }

2.3 二叉树非递归 后续

助记 :入栈向左一直走,判定(右子树是否访问),出栈访问(标记、置空);

 public void postOrder(TreeNode tree){
 	TreeNode cur=tree;//记录当前访问到了哪个节点
 	TreeNode r=null;//记录上一个visited的节点。
 	//因为因为从右边访问根节点,必定是右子树已经遍历
 	Stack<TreeNode> stack= new Stack<>();
 	while(cur!=null || !stack.empty()){
		if(cur!=null){
			// 入栈
			stack.push(cur);
			cur=cur.left;
		}else{
			 // 访问到栈顶元素的左子树为NULL时,走到这个分支
			 //所以需要获取栈顶元素进行操作
		 // 只读取当前栈顶元素
		 cur=stack.peek();
		 // 右子节点 存在 且 未被访问过
		 if(cur.right!=null && cur.right!=r){
		 	// 继续右子节点  
		 	cur=cur.right;
		 	// stack.push(cur); 与上面重复可以不写
		 	// cur=cur.left;
		 	}else{
				//cur .right 为空 或者被访问过了 访问根节点
				cur=stack.pop();
				System.out.println(cur.val);
				//记录当前访问的节点时cur(走到下一个循环时,就可以知道		
				//cur上一个访问的节点)
				r=cur;
			//把cur置空,进入下一次循环,
			//直到栈内无元素且cur为空时完成遍历
			cur=null;
			}
		 }//else
	}//while
 }

图片
aa

2.4 层序遍历

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res=new ArrayList<>();
        Queue<TreeNode> queue=new LinkedList<>();
        if(root==null) return res;
        queue.offer(root);
        while(!queue.isEmpty()){
            int size=queue.size();
            List<Integer> list=new ArrayList<>(size);
            for(int i=0;i<size;i++){
                TreeNode node= queue.poll();
                list.add(node.val);
                if(node.left!=null) queue.offer(node.left);
                if(node.right!=null) queue.offer(node.right);
            }
            res.add(list);
        } 
        return res;
        
    }
}

3.树旋转问题 右旋转

22
当节点进行右旋转时 左旋转同理
1.问题分析
当符合右旋条件时,
2.如果需要旋转的节点。
–2.1 如果当前节点的左子树的右子树的高度大于当前节点的左子树的左子树的高度。
对当前节点的左子树进行左旋操作。
aa1

aa
2.2.再对当前节点进行右旋操作。
反之 正常右旋转
在22述
需要添加return
当节点进行左旋转时
例子: 先对根节点的左子树左旋,得到 :
55d

3.1 avl 树代码链接

avl树代码链接
调整原则

  1. 降低树的高度。
  2. 保持二叉树的性质。

4.单调队列 解决滑动窗口最大值

理解
原图
在;ll述

//一般都习惯写数组索引从0开始 模板要改一下
  for (int i = 0; i < n; i++) { 
     //i-k+1 代表滑动窗口内第一个元素的索引
      // b.front() 是队头 当对头的元素从窗口划出时,队头元素出队
      if (!b.empty() && b.front() < i - k + 1)
          b.pop_front();
      //判断队头是否需要出队 
      //也就是下一个元素 >=队尾元素 那么队尾出队,再进行元素入队,
      while (!b.empty() && a【i】 >=a【b.back())
      b.pop_back();
      //维护队列单调性  
  //下标入队,便于队头出队
      b.push_back(i);
      //取队头作为最大窗口元素
      //i>k-2 表示滑动窗口内 元素满了 才取最大值。
      if (i > k - 2) printf("%d ", a【b.front());
    
  }

11

5. 滑动窗口思想

滑动窗口思想总结。

关键词 最长 最小 (无重复字符) 子串 子数组 序列

6.0求总方式数动态规划

求总方式数的问题
多数情况用到加法原理
例子不同路径问题
求走到a 有多少种不同的路径
[ ] [ a ]
[ b ] [ x ]
路线只能向下,或者向右。到x右多少种
需要考虑 走到a有多少种方法在走一步就到了x
考虑走到 b有多少种方法,再走一步到x
x=a(到a的种数)+b(到b的种数)

如果是
x=a+1 + b+1
变成了走到x需要多少步
那就是到a的步数+1到x

6.求最值动态规划

适用动态规划的问题
aasd

6.1 确定状态

开数组 开多大
两个意识:

  • 最后一步
    – 最优策略中的最后一个决策
    – 最优策略中用的最后一枚硬币ak
    (拼出i块钱,最后一枚硬币应该是谁呢)
  • 子问题
    – 最少的硬币拼出的更少面值27-ak

6.2 转移方程

设f[x]=最少用多少枚硬币拼出x

  • f[x]=min{f[x-2]+1,f[x-5]+1,f[x-7]+1}

6.3 初始条件和边界

	拼出x所需要的最少硬币数
	f[x]=min{f[x-2]+1,f[x-5]+1,f[x-7]+1};
  • 两个问题:
    1. x-2,x-5,x-7 小于0怎么办?
    2. 什么时候停下来
  • 如果不能拼出Y,就定义f[Y]=正无穷
    例子:f[-1]+f[-2]=正无穷
  • 所以 f[1]=min{f[-1]+1,f[-4]+1,f[-6]+1}=正无穷
  • 初始条件: f[0]=0,
  • 0块钱最少用0枚硬币
    用状态方程算不出来的,需要手动定义
    如果 f[0]=min{f[-2]+1,f[-5]+1,f[-7]+1}=正无穷
    但是明明知道f[0]=0,所以需要手动定义初始条件
    f[1]是拼不出来的,所以正无穷,f[2]=1
  • 不要数组越界

6.4 计算顺序

f[x]=min{f[x-2]+1,f[x-5]+1,f[x-7]+1};

初始条件 :f[0]=0
然后计算 f[1],f[2]…f[27]
当我们计算到f[x]时,f[x-2],f[x-5],f[x-7]都已经得到结果了。

/**
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。
**/
class Solution {
    public int coinChange(int[] coins, int amount) {
			// 开数组
			int[] f=new int[amount+1];
			// 初始化 
			f[0]=0;
		int numCoins=coins.length;
			// 计算面值f[1] ...f[27] 需要的硬币
			for(int i=1;i<=amount;i++){
				f[i]=Integer.MAX_VALUE;
				//根据公式 f[x]=min{f[x-coins[0]]+1,f[x-coins[1]]+1,
				// .....,f[x-coins[j-1]]+1,}
				for(int j=0;j<numCoins;j++){
					// 考虑 边界
					if(i>=coins[j]&&f[i-coins[j]]!=Integer.MAX_VALUE){
				f[i]=Math.min(f[i],f[i-coins[j]]+1);	
					}
				}
				
			}
			return f[amount]==Integer.MAX_VALUE?-1:f[amount];			
			
    }
}

7.异或问题

小部分疑问题理解

8.排序

8.1 快排

  public static void quickSort(int[] arr, int low, int high) {
        if(low>=high) return;
        int pivotpos=partition(arr,low,high);
        quickSort(arr,low,pivotpos-1);
        quickSort(arr,pivotpos+1,high);
    }

    private static int partition(int[] arr, int low, int high) {
        //注意是arr[low] 不是arr[0];
        int pivot=arr[low];
        
        while (low<high){
            while(low<high&&arr[high]>=pivot) 
                high--;
            arr[low]=arr[high];
            while(low<high&&pivot>=arr[low])
                low++;
            arr[high]=arr[low];
        }
            arr[low]=pivot;
        return low;
    }

8.2 堆排序

    public static void heapSort(int[] arr){
            //建堆 o(n)
        for(int i=arr.length/2-1;i>=0;i--){
            heapify(arr,arr.length,i);
        }

        //排序
        for(int i=arr.length-1;i>0;i--){
            swap(arr,i,0);
            //i 维护数组的长度
            heapify(arr,i,0);
        }
    }
    /**
     *adjustheap()函数的时间复杂度k=log(n)
     * @param arr 数组
     * @param length 待维护数组的长度
     * @param i 需要维护的索引
     */
    public static void heapify(int[] arr,int length,int i){
            int largestIndex=i;
            int lson=i*2+1,rson=i*2+2;
            if(lson<length&&arr[lson]>arr[largestIndex])
                largestIndex=lson;
            if(rson<length&&arr[rson]>arr[largestIndex])
                largestIndex=rson;
            if(largestIndex!=i){
                swap(arr,largestIndex,i);
                heapify(arr,length,largestIndex);
            }
    }
    public static void swap(int[] arr,int left,int right){
        arr[left]=arr[left]^arr[right];
        arr[right]=arr[left]^arr[right];
        arr[left]=arr[left]^arr[right];
    

二.排序重建堆
  在取出堆顶点放到对应位置并把原堆的最后一个节点填充到堆顶点之后,需要对堆进行重建,只需要对堆的顶点调用adjustheap()函数。
  每次重建意味着有一个节点出堆,所以需要将堆的容量减一。adjustheap()函数的时间复杂度k=log(n),k为堆的层数。所以在每次重建时,随着堆的容量的减小,层数会下降,函数时间复杂度会变化。重建堆一共需要n-1次循环,每次循环的比较次数为log(i),则相加为:log2+log3+…+log(n-1)+log(n)≈log(n!)。可以证明log(n!)和nlog(n)是同阶函数:
  ∵(n/2)n/2≤n!≤nn,∵(n/2)n/2≤n!≤nn,
  ∴n/4log(n)=n/2log(n1/2)≤n/2log(n/2)≤log(n!)≤nlog(n)∴n/4log⁡(n)=n/2log⁡(n1/2)≤n/2log⁡(n/2)≤log⁡(n!)≤nlog⁡(n)
  所以时间复杂度为O(nlogn)

9.前缀和-差分

9.1前缀和

原数列
1 2 3 4 5 6 7 8 9
前缀和数列
1 3 6 10 15 21 28 36 45
用a表示原数列
用s表示前缀和数列

前i个和
s[i]=a[1]+a[2]+…+a[i]
时间复杂度 O(1)

应用 求L-R数列之间的和:
原始方式

int sum=0;
for(int i=L;i<=R;i++)
 sum+=arr[i];

前缀和方式

res=s[R]-s[L-1];

s[L]=a[1]+a[2]+....+a[L-1]+a[L];
s[L-1]=a[1]+a[2]+....+a[L-1];
S[R]=a[1]+....+a[L-1]+a[L]+....+a[R];
L-R之间的和


10.拓扑排序

步骤~

(1)从AOV网中选择一个没有前驱的顶点并且输出;
(2)从AOV网中删去该顶点,并且删去所有以该顶点为尾的弧;
(3)重复上述两步,直到全部顶点都被输出,或AOV网中不存在没有前驱的顶点。

9.1教学计划制定

jxjh
拓扑排序
c1,c2,c3,c4,c5,c6,c7

ccc
结果
cc2

c0,c1,c2
此时构成了环 因此拓扑排序不能进行下去 循环退出。
此时不是AOV网

9.2 编程完成拓扑排序

设计数据结构
1.图的存储结构:采用邻接表存储,在顶点表中增加一个入度域in。
ac
2.栈S:存储所有无前驱的顶点。也可以用队列
ac
123
拓扑排序为代码
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
考研 数据结构学习路线可以按照以下步骤进行: 1. 首先,建议先阅读一本系统的数据结构教材,如引用\[3\]所提到的那本书。这本书从趣味故事引入算法复杂性计算及数据结构基础内容,涵盖了线性结构、树形结构和图形结构,以及各种基本应用和高级应用。 2. 在阅读教材的过程,重点理解每种数据结构的定义、特点和基本操作。理解数据结构的思想和原理是非常重要的,这样才能更好地应用和解决问题。 3. 掌握数据结构的实现方法和常见的算法。这包括掌握各种数据结构的插入、删除、查找和排序等操作的代码实现。但是记住,学习数据结构不是死记硬背代码,而是要理解代码的逻辑和思路。 4. 在学习过程,可以通过编写代码来加深对数据结构的理解。可以使用C++语言来实现数据结构的代码,如引用\[3\]所提到的那本书是基于C++语言编写的。 5. 最后,进行练习和实践。通过解决一些实际问题和编写一些小项目来巩固所学的数据结构知识。这样可以提高对数据结构的应用能力和解决问题的能力。 总之,数据结构学习路线是先理解基本概念和原理,然后掌握代码实现,最后通过练习和实践来巩固和应用所学的知识。记住,重要的是理解和掌握数据结构的思想和解决问题的方法,而不是死记硬背代码。 #### 引用[.reference_title] - *1* *2* *3* [算法与数据结构学习路线](https://blog.csdn.net/londa/article/details/119063364)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值