上一篇文章程序员面试闯关(一):字符串匹配+排序+查找列举说明了各种常见的排序算法等算法基础,这里,主要分析下数据结构相关的基础和注意点。

一、线性表

1. 数组(顺序存储结构)

效率分析

查找:O(1)【数组的存储是连续内存空间】

插入和删除:

最好情况:O(1)

①插入到最后一个位置 ②删除最后一个元素

最坏情况:O(n)

①插入到第一个位置 ②删除第一个元素

平均情况:O((n-1)/2) = O(n)

整体分析:

适合:存数据,取数据【无须为了表示表中元素之间的逻辑关系而增加额外的存储空间,可以快速地存取表中任一位置的元素】

不适合:插入和删除数据【需要频繁移动元素】

2. 链表(链式存储结构)

头指针与头结点的异同:

头指针:

是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针。

头指针具有标识作用,所以常用它来冠以链表的名字

无论链表是否为空,头指针均不为空。头指针是链表的必要元素。

头结点:

头结点是为了操作的统一和方便而设定的,放在第一个元素的结点之前,其数据域一般无意义(一般用于存放链表的长度)

有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其他结点的操作就统一。

头结点不一定是链表的必要元素。

效率分析

查找:O(n)

插入和删除:O(1)

优势:

相比于数组:数组需要预先分配存储空间,容易造成空间浪费和溢出。而单链表不需要分配空间,元素个数也不受限制。

细分

单链表:就是上述这种,内存空间分配不连续。

静态链表:【就是为了给没有指针的语言设计的方案

】用数组去模拟链表,内存空间。具体实现:设计一个结构体,创建一个结构体数组,这个数组很长,足够你用了,然后,用两个结构体实例,一个作为数组头,一个作为数组尾,用于说明实际的数组长度:

typedef struct{

ElemType data;

int cur; //表示下一个元素的下标(一般会指向它下一个位置,如果是末尾,则指向‘0’)

}

优点:要插入或删除元素,只需要先在数组末尾添加这个元素,然后,修改这个元素的cur和插入点元素的cur即可。避免了元素移位的复杂过程。

缺点:表长度难以确定的问题;失去了数组的随机存取快的特性。

循环链表

双向链表

二、栈和队列

1. 栈

概念:栈是限定仅在表尾进行插入和删除操作的线性表。

结构:

顺序结构(一个栈):

typedef struct{

int data[MAXSIZE];

int top; //表示栈顶下标(-1表示:栈空)

}Stack

顺序结构(两个栈共享空间):

typedef struct{

int data[MAXSIZE];

int topA; //表示栈A的栈顶下标(-1表示:栈空)

int topB; //表示栈B的栈顶下标(MAXSIZE 表示:栈空)

}DoubleStack

链式结构:

typedef struct StackNode{///栈元素

int data;

struct StackNode *next;

}StackNode , *LinkStackPtr;

typedef struct LinkStack{//链栈

LinkStackPtr top;///指向栈顶的指针

int count;//栈大小(0表示:空栈)

}LinkStack;

2. 队列

概念:队列是只允许一端进行插入操作,另一端进行删除操作的线性表。

循环队列:

目的:为了解决存储空间的循环利用的问题。

性质:

队列满的条件是:(rear+1)%QueueSize == front 【其中‘rear’表示队尾,‘font’表示队头】

结构如下:

typedef struct{

int data[MAXSIZE];

int front; //头指针(初始化为:0)

int rear; //尾指针(初始化为:0,队列不空,就指向队列最后一个元素的下一个位置)

}

操作:

入队:

Q->rear = (Q->rear + 1)%MAXSIZE;

出队:

Q->front = (Q->front + 1)%MAXSIZE;

链队列:

结构:

typedef struct QNode{

int data;

struct QNode *next;

}QNode , *QueuePtr;

typedef struct{

QueuePtr front, rear; /*队头,队尾指针*/

}

三、树

1. 各种概念重温

结点层次(Level):根为第一层,根的孩子为第二层。

其双亲在同一层的结点互为堂兄弟。

森林:m(m>=0)课互不相交的树的集合。

2. 一般树的存储结构

双亲表示法【缺点:找父亲O(1),找孩子O(n)】

其实是对象数组(包含其父结点的下标)

typedef struct PTNode{

int data;

int parent;//父结点的下标,‘-1’表示没有父结点,本身是根结点

}

typedef struct{

PTNode nodes[100]; //结点数组

int r, n; //根位置 , 结点数

}

孩子表示法【缺点:①找父亲O(n),找孩子O(1) ②要加孩子,结构需要改,遍历更加复杂】

其实是将n个结点用一个长度为n的结点数组装起来,每个结点的子结点用一条属于这个结点的单链表来表示,如果是叶子结点,那么则没有子链表,它的firstchild为null。

typedef struct CTNode{ ///孩子结点

int child;

struct CTNode *next;

} *ChildPtr;

typedef struct{ 表头结构

int data;

ChildPtr firstchild;

} CTBox;

typedef struct{ /树结构

CTBox nodes[100]; //结点数组

int r,n; ///根位置和结点数

}

孩子兄弟表示法【缺点,还是难找到父结点 ,如果要改,需要加多一个指针】

一个结构体,两个指针(一个纵向,一个横向),相当于他的“长子”和“二弟”。

typedef struct CSNode{

int data;

struct CSNode *firstChild;///它的第一个孩子结点

struct CSNode *rightChild;///它本身的兄弟结点

}

3. 二叉树

相关概念:

斜树:所有结点都在左子树 称为左斜树;所有结点都在右子树 称为右斜树。

完全二叉树的特点:

叶子结点只能在最下两层。

最下层叶子一定集中在左部连续位置。

如果结点度数为1,此结点必定只有左子树。

同样结点数的二叉树,完全二叉树的深度最小。

二叉树的性质:

在二叉树的第i层上至多有2^(i-1)个结点

深度为k的二叉树至多有2^k -1个结点

任意一颗二叉树,n2(度数为2的结点) = n0(度数为0的结点)-1

树的边数(也叫:分支线总数)和结点数的关系:边数 = n-1 = n1+2*n2

具有n个结点的完全二叉树的深度为

表示:不大于logn的最大整数+1

如果放在一个数组中,那么,父子结点之间的下标关系如下:

二叉树相关操作

线索二叉树

定义:因为二叉链表只能知道某个结点的左右孩子结点的地址,而不知道它在遍历时的一个‘前驱’和‘后继’结点是谁。于是,将原来的结点结构中的左右孩子成员,在判断为null的情况下, 变为这个结点的前驱和后继结点。这样,一来充分利用了空间,二来方便了我们查找某个结点的前驱和后继结点。

结构:

typedef enum {LInk , Thread} PointerTag; //Link == 0 表示指向左右孩子,Thread==1 表示指向前驱和后继结点

typedef struct BiThrNode{

int data; //数据

struct BiThrNode *lchild , *rchild;//左右孩子指针(或者前驱后继结点)

PointerTag LTag; //左标志

PointerTag RTag; //右标志

} BiThrNode, *BiThrTree;

中序遍历示例

意义

如果实际中所用的二叉树需要经常遍历或查找结点时需要某种遍历序列中的前驱和后继,那么采用线索二叉链表的存储结构是不错选择。

二叉树、数、森林之间的转换

树转二叉树:

森林转二叉树:

二叉树转树:

二叉树转森林:

哈夫曼树

从树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称作 路径长度。【带权路径长度WPL最小的二叉树,称作 赫夫曼树】

四、图

1. 定义:G(V【顶点集合】, E【边的集合】)

2. 相关概念

完全图:任意两个点之间有边的图

简单图:无重复的边或顶点到自身的边

度:顶点的边数

3. 图的存储结构

邻接矩阵

存储方式:用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组存储图中的边或弧的信息。

优势:

容易判断两个顶点是否有边

要知道某个顶点的度,其实就是这个顶点vi 在邻接矩阵中的第i行(或第i列)的元素之和。

求顶点vi的所有邻接点,只需要遍历矩阵第i行。

图解(无向连通图为例):

邻接表

存储方式:用两个对象结构,一个来表示顶点,另一个则表示该顶点的邻接顶点权值信息。每个顶点对象,都是一个链表的表头结点,他后面接着的是他的所有邻接顶点的信息列表。如下图,表示每一个顶点的链表结构模型。

每一个顶点的邻接表

邻接矩阵和邻接表的比较:

当图中结点数目较小且边较多时,采用邻接矩阵效率更高。

当节点数目远大且边的数目远小于相同结点的完全图的边数时,采用邻接表存储结构更有效率。

五、二叉树和图的各个常考方法代码汇总(代码可直接复制运行)

1. 二叉树各个方法

package basic.tree;

import java.util.ArrayDeque;

import java.util.Queue;

import java.util.Scanner;

import java.util.Stack;

/**

* 二叉树

* Created by androidjp on 16/8/17.

*/

public class BiTree {

public String data;

public BiTree left;

public BiTree right;

public static Scanner scanner = new Scanner(System.in);

/**

* 以 先序遍历 的顺序进行元素插入

*

* @return 创建好的二叉树

*/

public static BiTree createTree() {

String s = scanner.next();

char ch = s.charAt(0);

if (ch == '#') {

return null;

}

BiTree root = new BiTree();

root.data = s;

root.left = null;

root.right = null;

//递归进行插入

root.left = createTree();

root.right = createTree();

return root;

}

/**

* 先序遍历二叉树

*

* @param tree

*/

public static void preTraverse(BiTree tree) {

if (tree == null)

return;

System.out.print(tree.data + " ");

preTraverse(tree.left);

preTraverse(tree.right);

}

/**

* 中序遍历二叉树

*

* @param tree

*/

public static void orderTraverse(BiTree tree) {

if (tree == null)

return;

orderTraverse(tree.left);

System.out.print(tree.data +" ");

orderTraverse(tree.right);

}

/**

* 后序遍历二叉树

*

* @param tree

*/

public static void postTraverse(BiTree tree) {

if (tree == null)

return;

postTraverse(tree.left);

postTraverse(tree.right);

System.out.print(tree.data + " ");

}

/**

* 二叉树的深度优先遍历

* 【二叉树不同于图,图需要标记元素是否已经被遍历过,因为可能存在环,而树则不用考虑环的问题,于是少了判断这一步】

* 使用栈 遍历

*

* @param tree

*/

public static void depthFirstTraverse(BiTree tree) {

Stack stack = new Stack<>();

stack.push(tree);

while (!stack.empty()) {

tree = stack.peek();

System.out.print(tree.data + " ");

stack.pop();

//注意 元素入栈先后顺序: right -> left

if (tree.right!= null){

stack.push(tree.right);

}

if (tree.left != null) {

stack.push(tree.left);

}

}

}

/**

* 广度优先遍历(也称为:层次遍历)

* 利用队列

* @param tree

*/

public static void breadFirstTraverse(BiTree tree){

Queue queue = new ArrayDeque<>();

queue.add(tree);

while(!queue.isEmpty()){

tree = queue.poll();

System.out.print(tree.data + " ");

if (tree.left!=null){

queue.add(tree.left);

}

if (tree.right!=null){

queue.add(tree.right);

}

}

}

public static void main(String[] args) {

BiTree tree = new BiTree();

///(根节点 -> 左孩子 -> 右孩子)创建二叉树,如输入: 1 2a 3a # 4b # # 3b # # 2b # #

tree = createTree();

BiTree ptr = tree;

//先序遍历

preTraverse(ptr);

System.out.println();

//中序遍历

ptr = tree;

orderTraverse(ptr);

System.out.println();

//后序遍历

ptr = tree;

postTraverse(ptr);

System.out.println();

//深度优先遍历

ptr = tree;

depthFirstTraverse(ptr);

System.out.println();

//广度优先遍历

ptr = tree;

breadFirstTraverse(ptr);

System.out.println();

}

}

2. 图的广度和深度优先比遍历(邻接矩阵表示权值)

import java.util.ArrayList;

import java.util.LinkedList;

import java.util.List;

/**

* 使用邻接矩阵来表示'边'的图结构

* Created by androidjp on 16/9/8.

*/

public class MyGraph {

private ArrayList vertexList;//点集合

private int[][] edges; //邻接矩阵表示的边集合

private int numOfEdges; //边数

public MyGraph(int num){

vertexList = new ArrayList(num);

edges = new int[num][num];

numOfEdges = 0;

}

///边数

public int getNumOfEdges(){

return this.numOfEdges;

}

///结点数

public int getNumOfVertex(){

return this.vertexList.size();

}

//获取第i个结点

public V getVertex(int i){

return this.vertexList.get(i);

}

//获取i、j两个结点之间的权值

public int getWeight(int i, int j){

return this.edges[i][j];

}

//插入结点

public void insertVertex(V vertex) {

vertexList.add(vertexList.size(),vertex);

}

//插入结点

public void insertEdge(int v1,int v2,int weight) {

edges[v1][v2]=weight;

numOfEdges++;

}

//删除结点

public void deleteEdge(int v1,int v2) {

edges[v1][v2]=0;

numOfEdges--;

}

//得到第一个邻接结点的下标

public int getFirstNeighbor(int index) {

for(int j=0;j

if (edges[index][j]>0) {

return j;

}

}

return -1;

}

//根据前一个邻接结点的下标来取得下一个邻接结点

public int getNextNeighbor(int v1,int v2) {

for (int j=v2+1;j

if (edges[v1][j]>0) {

return j;

}

}

return -1;

}

//私有函数,深度优先遍历

private void depthFirstSearch(boolean[] isVisited,int i) {

//首先访问该结点,在控制台打印出来

System.out.print(getVertex(i)+" ");

//置该结点为已访问

isVisited[i]=true;

int w=getFirstNeighbor(i);

while (w!=-1) {

if (!isVisited[w]) {

depthFirstSearch(isVisited,w);

}

w=getNextNeighbor(i, w);

}

}

//对外公开函数,深度优先遍历,与其同名私有函数属于方法重载

public void depthFirstSearch() {

boolean[] isVisited = new boolean[getNumOfVertex()];

for(int i=0;i

isVisited[i] = false;

for(int i=0;i

//因为对于非连通图来说,并不是通过一个结点就一定可以遍历所有结点的。

if (!isVisited[i]) {

depthFirstSearch(isVisited,i);

}

}

}

//私有函数,广度优先遍历

private void broadFirstSearch(boolean[] isVisited,int i) {

int u,w;

LinkedList queue=new LinkedList();

//访问结点i

System.out.print(getVertex(i)+" ");

isVisited[i]=true;

//结点入队列

queue.addLast(i);

while (!queue.isEmpty()) {

u=((Integer)queue.removeFirst()).intValue();

w=getFirstNeighbor(u);

while(w!=-1) {

if(!isVisited[w]) {

//访问该结点

System.out.print(getVertex(w)+" ");

//标记已被访问

isVisited[w]=true;

//入队列

queue.addLast(w);

}

//寻找下一个邻接结点

w=getNextNeighbor(u, w);

}

}

}

//对外公开函数,广度优先遍历

public void broadFirstSearch() {

boolean[] isVisited = new boolean[getNumOfVertex()];

for(int i=0;i

isVisited[i] = false;

for(int i=0;i

if(!isVisited[i]) {

broadFirstSearch(isVisited, i);

}

}

}

public static void main(String args[]) {

int n = 8, e = 9;//分别代表结点个数和边的数目

String labels[] = {"1", "2", "3", "4", "5", "6", "7", "8"};//结点的标识

MyGraph graph = new MyGraph(n);

for (String label : labels) {

graph.insertVertex(label);//插入结点

}

//插入九条边

graph.insertEdge(0, 1, 1);

graph.insertEdge(0, 2, 1);

graph.insertEdge(1, 3, 1);

graph.insertEdge(1, 4, 1);

graph.insertEdge(3, 7, 1);

graph.insertEdge(4, 7, 1);

graph.insertEdge(2, 5, 1);

graph.insertEdge(2, 6, 1);

graph.insertEdge(5, 6, 1);

graph.insertEdge(1, 0, 1);

graph.insertEdge(2, 0, 1);

graph.insertEdge(3, 1, 1);

graph.insertEdge(4, 1, 1);

graph.insertEdge(7, 3, 1);

graph.insertEdge(7, 4, 1);

graph.insertEdge(6, 2, 1);

graph.insertEdge(5, 2, 1);

graph.insertEdge(6, 5, 1);

System.out.println("深度优先搜索序列为:");

graph.depthFirstSearch();

System.out.println();

System.out.println("广度优先搜索序列为:");

graph.broadFirstSearch();

}

}

c语言闯关考试题讲解,程序员面试闯关(二):数据结构考点与细节分析相关推荐

  1. 直击招聘程序员面试笔试C语言深度解析,直击招聘 程序员面试笔试C++语言深度解析(直击招聘) pdf epub mobi txt 下载...

    直击招聘 程序员面试笔试C++语言深度解析(直击招聘) pdf epub mobi txt 下载 图书介绍 ☆☆☆☆☆ 李春葆,李筱驰 著 下载链接在页面底部 发表于2021-05-18 类似图书 点 ...

  2. 程序员面试一面、二面、三面区别

    文章目录 前言 一面 二面 三面 二面一定比一面难? 为什么我面了4,5面? GM面挂人吗? 是否所有的一面.二面.三面,都如此分工明确? 哪一面最重要 总结 前言 很多公司面试都分一面.二面.三面甚 ...

  3. 技术好却进不了大公司?程序员面试的几个小技巧请收好

    技术好却进不了大公司?程序员面试的几个小技巧请收好 有很多程序员,专业技术挺好,项目经验充足,简历上金碧辉煌,也面试了很多国内大厂,但就是没有一家能成功的.处境可以说是相当尴尬. 大家都知道程序员本身 ...

  4. python是c语言写的吗-先学C语言还是Python?资深程序员往往是这样建议的!

    编程是一门手艺, 学习编程首先要有目的,w3cschool先带大家了解一下自己需要学习什么吧! 0.学一门语言 其实学什么语言都可以,因为语言都是互通的,C语言也好, 对于初学者来说,Python 也 ...

  5. 没学c语言可以学python_先学C语言还是Python?资深程序员往往是这样建议的!

    编程是一门手艺, 学习编程首先要有目的,w3cschool先带大家了解一下自己需要学习什么吧! 0.学一门语言 其实学什么语言都可以,因为语言都是互通的,C语言也好, 对于初学者来说,Python 也 ...

  6. 为何程序C语言不会消亡?程序员学习C会白学吗?

    从世界范围的统计数据来看,C++虽然在不断衰退,但仍多年排在第三名.所以,在很长一段时间,C++都不会跌出前三的宝座. C++为什么不会消亡? 1.在游戏和工具领域仍然是主流 首先是游戏领域,Milo ...

  7. python和c先学哪个-先学C语言还是Python?资深程序员往往是这样建议的!

    编程是一门手艺, 学习编程首先要有目的,w3cschool先带大家了解一下自己需要学习什么吧! 0.学一门语言 其实学什么语言都可以,因为语言都是互通的,C语言也好, 对于初学者来说,Python 也 ...

  8. 程序员面试技巧:如何讲解自己做过的项目

    程序员面试干货:如何讲解自己做过的项目 说到简历,对于IT这种比较偏重技术的岗位,企业最看重的就是项目经验,尤其是曾经做过的项目,用过的技术越匹配越好! 一封好的简历就是事业的敲门砖,如果你目前正在处 ...

  9. 程序员,闯天下----一个文科生的10年编程之旅和创业之路

    我是超级表格的创始人,现在为这个产品设计架构和产品定位. 我的本科专业是教育学,硕士专业是市场营销. 在大学时没有上过计算机课程.毕业后开始自学计算机,做程序员,直到现在创业.说说我的10年编程之路吧 ...

最新文章

  1. Linux系统下如何安装软件包
  2. Linux System Programming --Chapter Six
  3. JDBC学习笔记 day1
  4. linux挂载cifs磁盘_linux使用windows磁盘,挂载共享目录
  5. python制作软件excel_利用Python制作一个 截图+Excel操作浏览器小工具
  6. python reduce函数怎么用的_我如何仅通过使用reduce函数在python中创建单...
  7. Robbin关于App class loader的总结(转帖)
  8. 谁说数据分析很难?看完这7大分析套路后,还学不会的来找我
  9. java file的用法_Java 关于File使用
  10. Unity3D基础39:人物角色模型
  11. 线程池是如何重复利用空闲线程的?
  12. Miller-rabin判素数
  13. 基于Javaweb的酒店客房预订管理系统
  14. 关于NaN、undefined、null——基础必懂
  15. SDUT—Python程序设计实验78(函数)
  16. Unbox failed! RequestError: Error: connect ETIMEDOUT 151.101.76.133:443
  17. 曾任5家上市企业大学校长李乾老师
  18. hashmap源码1.7
  19. 计算机能够运行的程序的大小不能超过内存,内存大小是影响电脑中运行的程序的速度...
  20. react native在growth stack中的角色思考

热门文章

  1. 液晶显示屏选型重点参数
  2. 几种有趣的电路设计(阻容降压,防过压,LLC,PFC,正激和反激电路)
  3. 微软在华十年:向中国倾斜
  4. Windows 服务程序(一)
  5. nvm介绍、nvm下载、安装与使用
  6. css中a标签超链接在新窗口中打开以及超链接去除/添加下划线
  7. 操作系统---linux
  8. mmo中匹配机制的思考与实现
  9. Python----数据分析-使用scikit-learn构建模型实训(wine数据集、wine_quality数据)
  10. C语言中strlen函数功能及用法