找到C++开发岗一步一个脚印
数据
是什么?
数据
是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合。
数据元素
是什么?
是组成数据的、有一定意义的基本单位,在计算机中通常作为整体处理,也被称为记录。
数据项
是什么?
一个数据元素
可以由若干个数据项
组成,是数据不可分割的最小单位。
数据对象
是什么?
是性质相同的数据
组成的集合,是数据
的子集。
不同数据元素
之间不是独立的,存在特定的关系,这个关系叫做结构
。
是相互之间存在一种或多种特定关系的数据元素的集合
。
从不同角度来看数据结构:分为逻辑结构和物理结构
逻辑结构分为:
数据的物理结构应该正确反映数据的逻辑结构,分为:
是指一组性质相同的值的集合以及定义在此集合上的操作的总称。
抽象数据类型(Abstract Data Type):一个数学模型以及定义在该数学模型上的一组操作。
算法是解决特定问题的步骤,对于计算机来说就是指令的有限序列,并且每天指令表示一个或多个操作。
算法的五个特性:
算法设计的理念:
事后统计方法
:通过设计好的测试程序和数据,利用计算机的计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。×
事前统计方法
:在计算机程序编制前,依据统计方法对算法进行评估。√
程序的运行时间取决于算法的好坏和问题的输入规模
分析程序运行时间时,只看执行的步骤
时间复杂度的定义:在进行算法分析时,语句的执行次数T(n)是关于时间规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间度量,记作T(n)= O(f(n))。它表示随着问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度。其中f(n)是问题规模为n的某个函数。
线性表(List):零个或多个数据元素的有限序列
线性表具有两种存储结构:
1、顺序存储结构
2、链式存储结构
存储分配方式 | 时间性能 | 空间性能 | |
---|---|---|---|
顺序存储结构 | 连续存储单元依次存储线性表的数据元素 | 查找:O(1) 插入与删除:O(n) | 预分配存储空间 |
单链表结构 | 链式存储结构,用一组任意的存储单元存放线性表的元素 | 查找:O(n) 插入与删除:找到位置的指针后,插入与删除的时间复杂度为O(1) | 不需要预分配 |
用一段地址连续的存储单元依次存储线性表的数据元素
线性表顺序存储的代码结构:
#define MAXSIZE 20 //存储空间初始分配量
typedef int ElemType;
typedef struct
{
ElemType data[MAXSIZE]; //数组存储数据元素
int length; //线性表当前长度
}
注:数组的长度是存放线性表的存放空间的长度,线性表的长度是线性表中数据元素的个数。
获得元素操作:
//获得元素操作
#define OK 1
#define Error 0
typedef int Status;
Status GetElem(SqList L, int i, ElemType* e)
{
if (i < 0 || i > L.length || L.length == 0)
{
return Error;
}
*e = L.data[i - 1];
return OK;
}
插入元素操作:
//插入元素操作
//在第i个元素位置插入数据元素e
Status ListInsert(SqList* L, int i, ElemType e)
{
//1 判断插入位置是否合理
if (i < 1 || i > L->length)
{
return Error;
}
//2 线性表的长度是否大于数组长度,大于了就不可插入了
if (L->length > MAXSIZE)
{
return Error;
}
//3 从最后一个元素开始遍历到第i个位置,将它们分别向后移动一个
if (i <= L->length)
{
for (int k = L->length - 1; k >= i - 1; k--)
{
L->data[k + 1] = L->data[k];
}
}
//4 将第i个位置插入数据元素
L->data[i - 1] = e;
//5 线性表长度增加1
L->length++;
return OK;
}
删除元素操作:
//删除操作
//删除第i个元素,并用e返回
Status ListDelete(SqList* L, int i, ElemType *e)
{
//1 如果线性表为空,抛出异常
if (L->length == 0)
{
return Error;
}
//2 如果删除位置不合理,抛出异常
if (i < 1 || i > L->length)
{
return Error;
}
//3 取出删除元素
*e = L->data[i - 1];
//4 从删除元素位置开始遍历到最后一个元素位置,分别将它们向前移动一个位置
if (i <= L->length)
{
for (int k = i - 1; k > L->length - 1; k++)
{
L->data[k] = L->data[k + 1];
}
}
//5 线性表的长度减1
L->length--;
return OK;
}
n个结点(包含数据域和指针域)链结成一个链表,即为线性表(a1,a2,…,an)的链式存储结构
数据域:储存数据元素信息的域
指针域:储存直接后继位置的域
头指针:链表中第一个结点的存储位置
头结点:为了操作的统一和方便设立,放在第一个元素的结点之前,通常数据域没有内容
单链表的存储结构:
#define Error 0
#define OK 1
typedef int ElemType;
typedef bool Status;
//单链表的存储结构
typedef struct Node
{
ElemType data;
struct Node* next;
}Node;
typedef struct Node* LinkList; //定义LinkList
单链表的读取:
//单链表的读取
Status GetElem(LinkList* L, int i, ElemType* e)
{
//1 声明一个指针p指向链表的第一个结点,初始化j从1开始
LinkList p = (*L)->next;
int j = 1;
//2 j
while (j < i && p)
{
p = p->next;
j++;
}
//3 若链表p末尾为空,说明第i个结点不存在
if (!p || j > i)
{
return Error;
}
//4 否则查找成功,返回结点p的数据
*e = p->data;
return OK;
}
单链表的插入:
//单链表的插入
Status ListInsert(LinkList* L, int i, ElemType e)
{
//1 声明一个p指针指向链表头结点,初始化j从1开始
LinkList p = (*L);
int j = 1;
//2 j
while (j < i && p)
{
p = p->next;
j++;
}
//3 若链表p末尾为空,说明第i个结点不存在
if (!p || j > i)
{
return Error;
}
//4 否则查找成功,系统生成一个新结点s
LinkList s = (LinkList)malloc(sizeof(LinkList));
//5 将数据元素e赋值给s->data
s->data = e;
//6 将s插入
s->next = p->next;
p->next = s;
return OK;
}
单链表的元素删除:
//单链表的删除
Status ListDelete(LinkList* L, int i, ElemType* e)
{
//1 声明一个p指针指向链表头结点,初始化j从1开始
LinkList p = (*L);
int j = 1;
//2 j
while (j < i && p->next)
{
p = p->next;
j++;
}
//3 若链表p末尾为空,说明第i个结点不存在
if (!(p->next) || j > i)
{
return Error;
}
//4 否则查找成功
LinkList q = p->next;
p->next = q->next;
free(q);
}
链表整表的创建,头插法:
//链表整表的创建,头插法
void CreateListHead(LinkList* L, int n)
{
//随机数种子
srand(time(0));
//1 声明一个p指针和计数器变量
LinkList p;
int i;
//2 初始化一个空链表
(*L) = (LinkList)malloc(sizeof(Node));
//3 让L的头结点的指针指向NULL
(*L)->next = NULL;
//4 循环加入
for (int i = 0; i < n; i++)
{
p = (LinkList)malloc(sizeof(Node));
p->data = rand() % 100 + 1;
p->next = (*L)->next;
(*L)->next = p;
}
}
链表整表的创建,尾插法:
//链表整表的创建,尾插法
void CreateListTail(LinkList* L, int n)
{
//随机数种子
srand(time(0));
//1 声明一个p指针和r指针
LinkList p, r;
//2 初始化一个空链表
(*L) = (LinkList)malloc(sizeof(Node));
//3 让r指向尾部的结点
r = *L;
//4 循环加入
for (int i = 0; i < n; i++)
{
p = (LinkList)malloc(sizeof(Node));
p->data = rand() % 100 + 1;
r->next = p;
r = p;
}
r->next = NULL;
}
单链表的整表删除:
//单链表的整表删除
Status ClearList(LinkList* L)
{
//1 声明一个指针p和q
LinkList p, q;
//2 将第一个结点赋值给p
p = (*L)->next;
//3 循环
while (p != NULL)
{
//3.1 将下一个结点赋值给q
q = p->next;
//3.2 释放p
free(p);
//3.3 将q赋值给p
p = q;
}
(*L)->next = NULL;
return OK;
}
单链表的信息打印:
//单链表的信息打印
Status LinstPrint(LinkList L)
{
//1 如果表长为0,抛出异常
if (L->next == NULL)
{
return Error;
}
//2 声明一个指针指向第一个结点
LinkList p = L->next;
while (p->next != NULL)
{
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
数组的元素由两个数据域组成,data:存放数据元素和cur:存放该元素的后继在数组中的下标;这种由数组描述的链表叫做静态链表
。
静态链表存储结构:
//静态链表模型
#define MAXSIZE 100
#define Error 0
#define OK 1
typedef int ElemType;
typedef bool Status;
typedef struct
{
ElemType data; //存放数据元素
int cur; //存放游标,后继元素的数组下标
}Component, StaticLinkList[MAXSIZE];
静态链表初始化:
//静态链表初始化
//将一维数组中的space中各分量链接成一个备用链表,space[0].cur 为头指针,"0"表示空指针
Status InitList(StaticLinkList space)
{
for (int i = 0; i < MAXSIZE - 1; i++)
{
space[i].cur = i + 1;
}
space[MAXSIZE - 1].cur = 0; //目前链表为空,最后一个元素为零
return OK;
}
计算链表长度:
//计算静态链表长度
int ListLength(StaticLinkList L)
{
int k = MAXSIZE - 1;
int count = 0;
while (L[k].cur)
{
k = L[k].cur;
count++;
}
return count;
}
静态链表的插入操作:
//静态链表的插入操作
//若备用空间链表非空,则返回分配的结点下标,否则返回0
int Malloc_SSL(StaticLinkList space)
{
int i = space[0].cur; //第一个备用链表的下标
if (space[0].cur)
{
space[0].cur = space[i].cur; //将下一个分量作为备用
}
return i; //返回第一个备用链表的下标
}
//进行插入
Status ListInsert(StaticLinkList L, int i, ElemType e)
{
int j, k, l;
k = MAXSIZE - 1; //k 为最后一个元素的下标,方便找到第一个元素
if (i < 1 || i > ListLength(L) + 1)
{
return Error; //插入位置有误
}
j = Malloc_SSL(L); //得到空闲分量的下标
if (j)
{
L[j].data = e; //给空闲分量的data赋值
for (l = 1; l < i - 1; l++)
{
k = L[k].cur; //找到第i个元素的前一位置
}
L[j].cur = L[k].cur; //将第i个元素之前的cur赋值给新元素的cur
L[k].cur = j; //第i个元素之前的cur指向新分配的下标
return OK;
}
return Error;
}
静态链表的删除操作
/*静态链表的删除*/
void Free_SSL(StaticLinkList space, int i)
{
space[i].cur = space[0].cur; //让即将删除的分量的cur指向空闲的第一个分量
space[0].cur = i; //把删除的分量下标给第一个元素的cur
}
/*实现删除操作*/
Status ListDelete(StaticLinkList L, int i)
{
if (i < 1 || i > ListLength(L) + 1)
{
return Error;
}
int k = MAXSIZE - 1;
for (int j = 1; j < i - 1; j++)
{
k = L[k].cur; //找到第i个元素之前的下标
}
int l = L[k].cur; //第i个元素的下标
L[k].cur = L[l].cur;
Free_SSL(L, l);
return OK;
}
将单链表终端结点的指针由指向NULL改为指向头结点,使整个单链表形成一个环,这种头尾相连的单链表成为单循环链表,简称循环链表
。
双向链表(double linked list)是在单链表的每个结点中,再设置了一个指向其前驱结点的指针域。
栈(stack)是限定仅在表尾进行插入和删除操作的线性表
后进先出(LIFO,Last In First Out)
栈的插入操作,叫做进栈,压栈,入栈
栈的删除操作,出栈,弹栈
栈模型
#define Error 0
#define OK 1
#define MAXSIZE 10
typedef int SElemType;
typedef bool Status;
//栈模型搭建
typedef struct
{
SElemType data[MAXSIZE];
int top;
}SqStack;
压栈、入栈
//压栈操作
Status Push(SqStack* S, SElemType e)
{
//栈满抛出异常
if (S->top == MAXSIZE - 1)
{
return Error;
}
S->top++;
S->data[S->top] = e;
return OK;
}
弹栈、出栈
//出栈
Status Pop(SqStack* S, SElemType* e)
{
if (S->top == -1)
{
return Error;
}
*e = S->data[top];
S->top--;
return OK;
}
队列(queue)是一种先进先出(First In First Out)的线性表,简称
FIFO。允许插入的一端为队尾,允许删除的一端为队头。
树(Tree)是n(n≥0)个结点的有限集。n=0时称为空树。在任意一颗非空树中:①有且仅有一个特定的称为根(Root)的结点;②当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2……Tm,其中每一个集合本身又是一颗树,并且称为根的子树(SubTree)。
树的结点包括一个数据元素及若干指向其子树的分支。结点拥有的子树数称为结点的度(Degree)。度为0的结点称为叶结点(Leaf)或终端结点;度不为0的结点称为非终端结点或分支结点。除根节点外,分支结点也称为内部结点。树的度是树内各结点度的最大值。
结点的子树的根称为该结点的孩子(Child),相应该结点称为其双亲(Parent)。同一双亲的孩子之间互称兄弟(Sibling)。结点的祖先是从根到该结点所经分支上的所有结点,反之,以某结点的根的子树的任意结点都是该结点的子孙。
结点的层次(Level):从根开始,根为第一层,根的孩子为第二层。某结点在l层,其子树的根在第l+1层。
堂兄弟:双亲在同一层的结点。
树的深度(Depth)或高度:树中结点的最大层次。
森林(Forest)是m(m≥0)棵互不相交树的集合。对树中每个结点来说,其子树的集合即为森林。
有序树:树中的结点的各子树是从左到右有序的,不能互换。
双亲表示法:在每个结点中,附设一个指示器指示其双亲结点在数组中的位置
/*树的双亲表示法结点结构定义*/
#define MAX_TREE_SIZE 100
typedef int TElemType; //树结点的数据类型
typedef struct PTNode //结点结构
{
TElemType data; //结点数据
int parant; //双亲位置
}PTNode;
typedef struct //树结构
{
PTNode nodes[MAX_TREE_SIZE]; //结点数组
int r, n; //根的位置和结点数
}PTree;
多重链表表示法:每个结点有多个指针域,每个指针指向一棵子树的根节点(孩子),两种方法:每个结点的指针域个数都为树的度;或者每个结点的指针域个数为自己的度。
孩子表示法:把每个结点的孩子结点排列起来,以单链表做存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空。然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中。
//孩子表示法,需要设计两种结构
/************************************************************************/
/* 孩子链表的孩子结点
* child | next
* child是数据域,用来存储某个结点在表头数组中的下标;
* next 是指针域存储指向某结点下一个孩子结点的指针
/************************************************************************/
typedef struct CTNode //孩子结点
{
int child;
struct CTNode* next;
}*ChildPtr;
/************************************************************************/
/* 表头数组的表头结点
* data | firstchild
* data 是数据域,存储某结点的数据信息
* firstchild 是头指针域,存储该结点孩子链表的头指针
/************************************************************************/
typedef struct
{
TElemType data;
ChildPtr firstchild;
}CTBox;
typedef struct //树结构
{
CTBox nodes[MAX_TREE_SIZE]; //结点数组
int r, n; //根的位置和结点数
}CTree;
孩子兄弟表示法
二叉树(Binary Tree)是n(n≥0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。
左(右)斜树:所有结点都是左(右)子树的二叉树。
满二叉树:在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层。
完全二叉树:对于一棵具有n个结点的二叉树按层序编号,如果编号为i(1≤i≤n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中的位置完全相同,则被称为完全二叉树。
二叉树的遍历(traversing binary tree):指从根结点出发,按照某种次序依次访问二叉树中的所有结点,使每个结点有且仅被访问一次。
二叉树的模型
typedef struct BiTNode
{
int data; //结点数据
struct BiTNode* lchild, * rchild; //左右孩子指针
}BiTNode, *BiTree;
限制从左到右
前序遍历:规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。
//前序遍历
void PreOrderTraverse(BiTree BiT)
{
if (BiT == NULL)
{
return;
}
cout << BiT->data << endl; //打印数据
PreOrderTraverse(BiT->lchild); //前序遍历左子树
PreOrderTraverse(BiT->rchild); //前序遍历右子树
}
中序遍历:规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。
//中序遍历
void PreOrderTraverse(BiTree BiT)
{
if (BiT == NULL)
{
return;
}
PreOrderTraverse(BiT->lchild); //前序遍历左子树
cout << BiT->data << endl; //打印数据
PreOrderTraverse(BiT->rchild); //前序遍历右子树
}
后序遍历:规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右叶子树,最后访问根结点。
//前序遍历
void PreOrderTraverse(BiTree BiT)
{
if (BiT == NULL)
{
return;
}
PreOrderTraverse(BiT->lchild); //前序遍历左子树
PreOrderTraverse(BiT->rchild); //前序遍历右子树
cout << BiT->data << endl; //打印数据
}
层序遍历:规则是若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。
线索二叉树:指向前驱或者后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树(Threaded Binary Tree)。
线索化:对二叉树以某种次序遍历使其变为线索二叉树的过程就是线索化。
线索二叉树的模型
线索化的过程
从树中的一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称为路径长度。树的路径长度就是从树根到每一结点的路径长度。
带权路径长度WPL最小的二叉树称为哈夫曼树。
构造哈夫曼树的算法:
概念,RAII与智能指针,各个版本智能指针从零实现,智能指针应用,Boost库
隐式类型转化
显示类型转化
强制类型转化
RTTI
语法糖式for循环
move和右值引用
新增的容器
c++11智能指针
多线程
Lambda表达式
vim
gcc
gdb
makefile
yam
select
poll
epoll
LT水平触发
ET边缘触发
建项
命名
语法格式
注释
文档化
模块化
OJ(Online Judge)
知名的有leetcode、牛客网、杭州电子科技大学OJ