六万字数据结构基础知识大总结(含笔试面试习题)

Posted /少司命

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了六万字数据结构基础知识大总结(含笔试面试习题)相关的知识,希望对你有一定的参考价值。

目录

前言

一、时间复杂度和空间复杂度

1,算法效率

2,时间复杂度

①时间复杂度的概念

②大O的渐进表示法

③常见时间复杂度计算举例

3,空间复杂度

二,线性表(单链表和双链表)

1,概念

2,顺序表

①概念

②接口实现

③创建顺序表

④打印顺序表

⑤获取顺序表有效长度

⑥在pos位置增加元素

⑦判断是否由某个元素、查找某个元素对应的位置,找不到返回-1

⑧获取 pos 位置的元素、给 pos 位置的元素设为/更新 value

⑨删除第一次出现的关键字key

3,链表

①概念

②链表的实现

③创建节点

④创建链表、打印链表

⑤查找是否包含关键字key是否在单链表当中、得到单链表的长度

 ⑥头插法、尾插法

⑦找到index-1位置的节点的地址、插入元素

⑧找到要删除的关键字的前驱、删除第一次出现关键字为key的节点

⑨ 删除所有值为key的节点

 ⑩清空链表

4,双向链表

①链表的实现

②构造节点和链表

③打印链表、求链表长度

④查询key

⑤头插法

⑥尾插法

⑦寻找插入节点

⑧插入元素

⑨删除元素

⑩清空链表

5,笔试习题

①反转一个单链表

②给定一个带有头结点 head 的非空单链表,返回链表的中间结点

③输入一个链表,输出该链表中倒数第k个结点

④删除链表中的多个重复值

⑤链表的回文结构

⑥合并两个链表

⑦输入两个链表,找出它们的第一个公共结点

⑧判断一个链表是否有环

⑨求有环链表的环第一个结点

三,栈和队列

1,栈

①概念

②栈的操作

③栈的实现

④实现mystack

2,队列

①概念

②队列的实现

③实现myqueue

四,二叉树

1,树

①概念

②树的基础概念

2,二叉树

①概念

②两种特殊的二叉树

③二叉树的性质

①二叉树的遍历

②前序遍历

​③中序遍历

④后序遍历

3,二叉搜索树

①概念

②操作-查找

③操作-插入

 ④操作-删除

⑤性能分析

4,笔试习题

①二叉树前序遍历

②二叉树中序遍历

③二叉树后序遍历

④检查两棵树是否相同

⑤二叉树的最大深度

⑥另一颗树的子树

⑦判断一颗树是否为一颗平衡二叉树

⑧对称二叉树

⑨二叉树镜像

五,优先级队列(堆)

1,二叉树的顺序存储

①存储方式

②下标关系

③二叉树顺序遍历

2,堆

①概念

②操作-向下调整

③建堆(建大堆为例)

3,堆的应用-优先级队列

①概念

②内部原理

③入队列

④出队列(优先级最高)

⑤返回队首元素(优先级最高)

4,堆排序

六,排序

1,概念

①排序

②稳定性

2,排序详解

①直接插入排序

②希尔排序

③直接选择排序

④堆排序

⑤冒泡排序

⑥快速排序

⑦归并排序

七,List

1,ArrayList简介

①ArrayList的构造

②ArrayList常见操作

③ ArrayList的遍历

八,Map和Set

1,搜索

①概念及场景

②模型

2,Map的使用

①关于Map的说明

②关于Map.Entry的说明

③Map 的常用方法说明

3,Set的使用

①常见方法说明

②TreeSet的使用案例


前言

数据结构作为比较难学习的学科来说,它也是我们未来作为程序员必须要牢牢掌握的一门学科,我们应该多画图,多写代码,才能有所收获,本篇博客可以作为入门级别的教学,里面会有图解和详细的代码,总结的内容即代表个人观点,还有部分数据结构内容没有总结,但足够作为一名数据结构的初学者学习,还有各种大厂的笔试题详解。本篇博客内容较长,建议收藏。如果你认为本篇博客写的不错的话,求点赞,求收藏,制作不易,废话不多说,让我们学起来吧!!!

一、时间复杂度和空间复杂度

1,算法效率

算法效率分析分为两种:第一种是时间效率,第二种是空间效率。时间效率被称为时间复杂度,而空间效率被 称作空间复杂度。 时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额 外空间,在计算机发展的早期,计算机的存储容量很小。所以对空间度很是在乎。但是经过计算机行业的 迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。

2,时间复杂度

①时间复杂度的概念

时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但 是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方 式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。

②大O的渐进表示法

void func1(int N)
   int count = 0;
   for (int i = 0; i < N ; i++) 
       for (int j = 0; j < N ; j++) 
           count++;
       
   
   for (int k = 0; k < 2 * N ; k++) 
       count++;
   
   int M = 10;
  while ((M--) > 0) 
       count++;
   
  System.out.println(count);

请计算一下func1基本操作执行了多少次?

  Func1 执行的基本操作次数 :

N = 10 F(N) = 130

N = 100 F(N) = 10210

N = 1000 F(N) = 1002010

实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里 我们使用大O的渐进表示法。

大O符号(Big O notation):是用于描述函数渐进行为的数学符号。

推导大O阶方法:

1、用常数1取代运行时间中的所有加法常数。

2、在修改后的运行次数函数中,只保留最高阶项。

3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。

使用大O的渐进表示法以后,Func1的时间复杂度为:

O(N^2)

N = 10 F(N) = 100

N = 100 F(N) = 10000

N = 1000 F(N) = 1000000

通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。 另外有些算法的时间复杂度存在最好、平均和最坏情况:

最坏情况:任意输入规模的最大运行次数(上界)

平均情况:任意输入规模的期望运行次数

最好情况:任意输入规模的最小运行次数(下界)

在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)

③常见时间复杂度计算举例

void func2(int N) 
    int count = 0;
    for (int k = 0; k < 2 * N ; k++) 
    count++;

    int M = 10;
    while ((M--) > 0) 
    count++;

    System.out.println(count);

void func3(int N, int M) 
     int count = 0;
for (int k = 0; k < M; k++) 
     count++;

for (int k = 0; k < N ; k++) 
     count++;

     System.out.println(count);

void func4(int N) 
   int count = 0;
   for (int k = 0; k < 100; k++) 
   count++;

   System.out.println(count);

void bubbleSort(int[] array) 
   for (int end = array.length; end > 0; end--) 
       boolean sorted = true;
       for (int i = 1; i < end; i++) 
           if (array[i -1] > array[i]) 
       Swap(array, i - 1, i);
               sorted = false;
           
       
       if (sorted == true) 
           break;
       
   

int binarySearch(int[] array, int value) 
   int begin = 0;
   int end = array.length - 1;
   while (begin <= end) 
       int mid = begin + ((end-begin) / 2);
       if (array[mid] < value)
           begin = mid + 1;
       else if (array[mid] > value)
           end = mid - 1;
       else
           return mid;
   
   return -1;

long factorial(int N) 
 return N < 2 ? N : factorial(N-1) * N;

// 计算斐波那契递归fibonacci的时间复杂度?
int fibonacci(int N) 
 return N < 2 ? N : fibonacci(N-1)+fibonacci(N-2);

3,空间复杂度

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少bytes 的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟实践复杂度 类似,也使用大O渐进表示法。

// 计算bubbleSort的空间复杂度?
void bubbleSort(int[] array) 
for (int end = array.length; end > 0; end--) 
     boolean sorted = true;
     for (int i = 1; i < end; i++) 
         if (array[i - 1] > array[i]) 
             Swap(array, i - 1, i);
             sorted = false;
         
     
     if (sorted == true) 
         break;
     
 

使用了常数个额外空间,所以空间复杂度为 O(1)

// 计算fibonacci的空间复杂度?
int[] fibonacci(int n) 
long[] fibArray = new long[n + 1];
fibArray[0] = 0;
fibArray[1] = 1;
for (int i = 2; i <= n ; i++) 
  fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
 
return fibArray;

动态开辟了N个空间,空间复杂度为 O(N)

// 计算阶乘递归Factorial的时间复杂度?
long factorial(int N) 
 return N < 2 ? N : factorial(N-1)*N;

递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间。空间复杂度为O(N)


二,线性表(单链表和双链表)

1,概念

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串.

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储 时,通常以数组和链式结构的形式存储。

2,顺序表

①概念

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数 据的增删查改。

顺序表一般可以分为:

静态顺序表:使用定长数组存储。

动态顺序表:使用动态开辟的数组存储。

静态顺序表适用于确定知道需要存多少数据的场景

静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用

相比之下动态顺序表更灵活, 根据需要动态的分配空间大小。

接口实现

class SeqList 
    // 打印顺序表
    public void display() 
        
    
    // 在 pos 位置新增元素
    public void add(int pos, int data) 
        
    
    // 判定是否包含某个元素
    public boolean contains(int toFind) 
        
        return true; 
    
    // 查找某个元素对应的位置
    public int search(int toFind) 
        return -1; 
        
    
    // 获取 pos 位置的元素
    public int getPos(int pos)  
        return -1; 
    
    
    // 给 pos 位置的元素设为 value
    public void setPos(int pos, int value) 
        
        
    
    //删除第一次出现的关键字key
    public void remove(int toRemove) 
        
    
    // 获取顺序表长度
    public int size() 
        return 0; 
    
    
    // 清空顺序表
    public void clear() 
        
    

③创建顺序表

    public int[] elem;
    public int usedSize;//有效的数据个数
 
    public MyArrayList() 
        this.elem = new int[10];
    

④打印顺序表

 // 打印顺序表
    public void display() 
        for (int i = 0; i < this.usedSize; i++) 
            System.out.print(this.elem[i]+" ");
        
        System.out.println();
    

⑤获取顺序表有效长度

// 获取顺序表的有效数据长度
    public int size() 
        return this.usedSize;
    

⑥在pos位置增加元素

 

public boolean isFull() 
        return this.usedSize == this.elem.length;
    
    // 在 pos 位置新增元素
    public void add(int pos, int data) 
        if(pos < 0 || pos > usedSize) 
            System.out.println("pos 位置不合法!");
            return;
        
        if(isFull()) 
            this.elem = Arrays.copyOf(this.elem,2*this.elem.length);
        
        //3、
        for (int i = this.usedSize-1; i >= pos ; i--) 
            this.elem[i+1] = this.elem[i];
        
        this.elem[pos] = data;
        this.usedSize++;
    
public static void main(String[] args) 
        MyArrayList myArrayList = new MyArrayList();
        myArrayList.add(0,1);
        myArrayList.add(1,2);
        myArrayList.add(2,3);
        myArrayList.add(3,4);
        myArrayList.display();

⑦判断是否由某个元素、查找某个元素对应的位置,找不到返回-1

  // 判定是否包含某个元素
    public boolean contains(int toFind) 
        for (int i = 0; i < this.usedSize; i++) 
            if(this.elem[i] == toFind) 
                return true;
            
        
        return false;
    
 public int search(int toFind) 
        for (int i = 0; i < this.usedSize; i++) 
            if(this.elem[i] == toFind) 
                return i;
            
        
        return -1;
    

获取 pos 位置的元素、给 pos 位置的元素设为/更新 value

 public int getPos(int pos) 
        if(pos < 0 || pos >= this.usedSize) 
            System.out.println("pos 位置不合法");
            return -1;//所以 这里说明一下,业务上的处理,这里不考虑  后期可以抛异常
        
        if(isEmpty()) 
            System.out.println("顺序表为空!");
            return -1;
        
        return this.elem[pos];
    
public boolean isEmpty() 
        return this.usedSize==0;
    
 public void setPos(int pos, int value) 
        if(pos < 0 || pos >= this.usedSize) 
            System.out.println("pos位置不合法");
            return;
        
        if(isEmpty()) 
            System.out.println("顺序表为空!");
            return;
        
        this.elem[pos] = value;
    

⑨删除第一次出现的关键字key

 public void remove(int toRemove) 
        if(isEmpty()) 
            System.out.println("顺序表为空!");
            return;
        
        int index = search(toRemove);
        if(index == -1) 
            System.out.println("没有你要删除的数字!");
            return;
        
        for (int i = index; i < this.usedSize-1; i++) 
            this.elem[i] = this.elem[i+1];
        
        this.usedSize--;
        //this.elem[usedSize] = null; 如果数组当中是引用数据类型。
    
public static void main(String[] args) 
        MyArrayList myArrayList = new MyArrayList();
        myArrayList.add(0,1);
        myArrayList.add(1,2);
        myArrayList.add(2,3);
        myArrayList.add(3,4);
        myArrayList.remove(3);
        myArrayList.display();
 
    

3,链表

①概念

链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。

链表分类

②链表的实现

// 1、无头单向非循环链表实现
public class SingleLinkedList 
     //头插法
 

     public void addFirst(int data)
     //尾插法

     public void addLast(int data)
     //任意位置插入,第一个数据节点为0号下标
 

     public boolean addIndex(int index,int data)
     //查找是否包含关键字key是否在单链表当中
 

     public boolean contains(int key)
     //删除第一次出现关键字为key的节点
 

     public void remove(int key)
     //删除所有值为key的节点
 

     public void removeAllKey(int key)
     //得到单链表的长度
 

     public int size()
 

     public void display()
 

     public void clear()
 

 

③创建节点

 public ListNode head;//链表的头引用
lass ListNode 
    public int val;
    public ListNode next;//null
 
    public ListNode(int val) 
        this.val = val;
    

④创建链表、打印链表

 public void createList() 
        ListNode listNode1 = new ListNode(12);
        ListNode listNode2 = new ListNode(23);
        ListNode listNode3 = new ListNode(34);
        ListNode listNode4 = new ListNode(45);
        ListNode listNode5 = new ListNode(56);
        listNode1.next = listNode2;
        listNode2.next = listNode3;
        listNode3.next = listNode4;
        listNode4.next = listNode5;
        //listNode5.next = null;
        this.head = listNode1;
    
public void display() 
        //this.head.next != null
        ListNode cur = this.head;
        while (cur != null) 
            System.out.print(cur.val+" ");
            cur = cur.next;
        
        System.out.println();
    
  public static void main(String[] args) 
        MyLinkedList myLinkedList = new MyLinkedList();
        //myLinkedList.createList();
        myLinkedList.addLast(12);
        myLinkedList.addLast(23);
        myLinkedList.addLast(34);
        myLinkedList.addLast(45);
        myLinkedList.addLast(56);
        myLinkedList.display();
 
       
    

⑤查找是否包含关键字key是否在单链表当中、得到单链表的长度

public boolean contains(int key)
        ListNode cur = this.head;
        while (cur != null) 
            if(cur.val == key) 
                return true;
            
            cur = cur.next;
        
        return false;
    
  public static void main(String[] args) 
        MyLinkedList myLinkedList = new MyLinkedList();
        //myLinkedList.createList();
        myLinkedList.addLast(12);
        myLinkedList.addLast(23);
        myLinkedList.addLast(34);
        myLinkedList.addLast(45);
        myLinkedList.addLast(56);
        myLinkedList.display();
        boolean flg = myLinkedList.contains(56);
 
    
public int size()
        int count = 0;
        ListNode cur = this.head;
        while (cur != null) 
            count++;
            cur = cur.next;
        
        return count;
    
public static void main(String[] args) 
        MyLinkedList myLinkedList = new MyLinkedList();
        //myLinkedList.createList();
        myLinkedList.addLast(12);
        myLinkedList.addLast(23);
        myLinkedList.addLast(34);
        myLinkedList.addLast(45);
        myLinkedList.addLast(56);
        System.out.println(myLinkedList.size());
        
    

 ⑥头插法、尾插法

//头插法
    public void addFirst(int data)
        ListNode node = new ListNode(data);
        node.next = this.head;
        this.head = node;
        /*if(this.head == null) 
            this.head = node;
        else 
            node.next = this.head;
            this.head = node;
        */
    
 pub

以上是关于六万字数据结构基础知识大总结(含笔试面试习题)的主要内容,如果未能解决你的问题,请参考以下文章

Python基础知识点六万字总结,爆肝一周熬夜完成建议收藏

2万字!90个前端开发面试必问基础大总结

❤️爆肝六万字最全总结Java数据库编程MyBatis(建议收藏)

❤️爆肝六万字最全总结Java数据库编程MyBatis(建议收藏)

❤️肝爆六万字 + 各种图解案例《大数据ETL开发之Kettle工具》小白保姆级教程❤️建议收藏

2021年中级前端笔试面试题总结(含答案解析)