2.1线性表的定义和特点
2.1.1线性表的定义
线性表是具有相同特性的数据元素的一个有限序列,记为
(a1,a2,...ai-1.ai,ai+1,...,an)
名词解释:
a1:线性起点(起始节点)
ai-1:ai的直接前趋,
ai+1:ai的直接后继
an:线性终点(终端结点)
n:表的长度(n=0为空表)
ai是抽象数据符号,不同情况下代表不同内容
2.1.2线性表的逻辑特征
在非空的线性表,有且仅有一个开始节点a1,它没有直接前趋,而仅有一个直接后继a2;
有且仅有一个终端节点an,它没有直接后继,而有且仅有一个直接前趋an-1;
其余内部节点ai(2<=i<=n-1)都有且仅有一个直接前趋ai-1和一个直接后继ai+1.
线性表是一种典型的线性结构。
2.2线性表的类型定义
2.2.1抽象数据类型线性表的定义如下:
ADT List{
数据对象:D={ai|ai属于ELemset,(i=1,2,...,n,n>=0)}
数据关系:R={<ai-1,ai>|ai-1,ai属于D,(i-2,3,...,n)}
基本操作:
InitList(&L);DestoryList(&L);
ListInsert(&L,i,e);
ListDelete(&L,i,&e);
....
}ADT List
2.2.2线性表的基本操作:
InitList(&L)
操作结果:构造一个空的线性表L。
DestoryList(&L)
初始条件:线性表L已经存在;
操作结果:销毁线性表L。
ClearList(&L)
初始条件:线性表L已经存在。
操作结果:将线性表L重置为空表
ListEmpty(L)
初始条件:线性表L已经存在
操作结果:若线性表L为空表,返回TRUE,否则,返回FALSE;
ListLength(&L)
初始条件:线性表L已经存在
操作结果:返回L中数据元素个数。
GetElem(L,i,&e);
初始条件:L已经存在,1<=i<=n
操作结果:用e返回L中第i个元素的值。
LocateElem(L,e,compare())
初始条件:L已经存在,compare()是数据元素判定函数
操作结果:返回L中第1个于e满足compare()的数据元素的位序。若不存在则返回0;
PriorElem(L,cur_e,&pre_e)
初始条件:L已经存在
操作结果:若cur_e是L的元素,且不是第一个,则用Pre_e返回它的前趋,否则操作失败,pre_e无意义;
NextElem(L,cur_e,&next_e)
初始条件:L已经存在
操作结果:若cur_e是L的元素,且不是第一个,则用next_e返回它的前趋,否则操作失败,next_e无意义;
ListInsert(&L,i,e)
初始条件:L已经存在,1<=i<=n+1;
操作结果:在L的第i个位置之前插入新的数据元素e,L的长度+1。
ListDelete(&L,i,&e)
初始条件:L已经存在,1<=i<=n+1;
操作结果:删除L的第i个数据元素,并用e返回其值,L的长度-1.
ListTraverse(&L,visited())
初始条件:L已经存在
操作结果:依次对线性表中每个元素调用visited()
2.3线性表顺序的表示和实现
顺序存储定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构。
特点:依次存储,地址连续。知道某个元素的存储位置就可以计算其他元素的存储位置。
地址计算方式:
;
L为每个元素占用的存储单元。LOC(a1)为基地址。
(与数组类似)
2.3.1顺序表的定义
顺序表地址连续、依次存放、随机存取、类型相同和数组类似,故可用一维数组表示顺序表。
线性表长度可变,但数组长度不可动态定义,所以可用一变量表示顺序表的长度属性。
模板: (代码1静态数组,代码2动态数组)
#define LIST_INIT_SIZE 100 //线性表存储空间的初始分配量 typedef struct{ ElemType data[LIST_INIT_SIZE]; //使用静态数组 int length; //当前线性表长度 }SqList;//顺序表形式 /*ElemType :表示抽象数据类型,可以根据需要自己改*/
#define LIST_INIT_SIZE 100 //线性表存储空间的初始分配量 typedef struct{ ElemType *data; //使用动态数组 int length; //当前线性表长度 }SqList; L.data=(ElemType*)malloc(sizeof(ElemType)*LIST_INIT_SIZE); /*malloc(m)函数表示开辟m字节长度的地址空间,并返回这段空间的首地址 sizeof(ElemType)*LIST_INIT_SIZE表示计算申请空间的大小,数据类型乘以线性表最大长度 (ElemType*)表示将这些空间强制转换成ElemType*,,即data的类型 */
案例:多项式顺序存储结构类型定义(当线性表不只有一种元素时)
#define MAXSIZE 1000 //多项式可能达到的最大长度 /*线性表包含的数据*/ typedef struct { //多项式非零项的定义 float p; //系数 int e; //指数 } Ploynomial /*线性表的各种性质*/ typedef struct { Ploynomial *elem; //存储空间的基地址 int length; //多项式中当前项个数 }Sqlist; //多项式的顺序存储结构类型为Sqlist
使用malloc(m)、sizeof(x)函数,需要加载<stdlib.h>头文件
该头文件中可能还会用到的函数:free(p):释放p指针所指变量的存储空间,即彻底删除一个变量
补充数组动态分配,使用C++
分配空间:Elemtype *data=new 类型名T(初值列表); 初值可以省略
释放空间:delete 指针p;
线性表L的初始化
Status InitList Sq(SqList &L){ L.elem=new ElemType[MAXSIZE]; if(!L.elem)exit(OVERFLOW); L.length = 0; return OK: }
链表
基本概念:
每个结点分为数据域和指针域。
数据域 指针域 存储数据本身 存储后继结点的地址
分类:
单链表 | 只有一个指针域,最后一个元素指针域为空 |
双链表 |
两个指针域,分别存后继节点和前趋结点 |
循环链表 | 链表首尾相接,最后一个元素指针域存储头节点 |
头指针:(需截图)
指向第一个结点的指针
首元结点:
存储第一个元素的结点
头节点:
首元结点之前附加的一个结点,指针域指向首元结点,数据域可为空,也可存储表长等数值,但其不计入表长。
优点:使首元结点和其他结点一样都有前趋结点,无需特殊处理
②空表和非空表也可统一处理
空表:
①无头结点:头指针为空
②有头结点:头结点指针域为空
访问:通过头指针进入,遍历寻找结点,这是顺序存取法(顺序表是随机存取)
带头结点的单链表:
存储结构:
包括data(数据域),next(指针域)两个成员的结构体
typedef struct Lnode{ //声明结点类型和指向结点的指针类型 Elemtype data; //数据域 struct Lnode *next; //指针域 }Lnode,*LinkList; //LinkList为指向结构体Lnode的指针类型 //使用了嵌套定义,将指针定义为链表类型 //单词解释:L链表,node结点;link链,list表
如果有多个数据域:
typedef Struct{ char num[8]; //数据域 char name[8]; //数据域 int score; //数据域 }ELemtype typedef struct Lnode{ ElemType data; //数据域 struct Lnode *next; //指针域 }Lnode,*LintList;
定义:
Lnode *p; //定义结点指针p LinkList L: //定义链表L //两者等价
初始化:
构建空表
算法思路:(1)生成新结点作头结点,用头指针指向头结点
(2)将头结点指针域置空
Status InitList_L(LinkList &L){ L=new Lnode; //头指针指向新生成的头结点 L->next=NULL://头结点指针域置空 return OK; } //使用Status之前,要先定义此类型:typedef int status; //OK使用之前要先定义:#define OK 1
判断链表是否为空:
算法思路:判断头结点指针域是否为空
int ListEmpty(LinkList L){ if(L->next) return 0; //非空,返回1 else return 1; //空,返回0
单链表的销毁(头结点、头指针均不存在了)
算法思路:从头指针开始,依次释放所有结点
(1)创建指针p指向头结点
栈
栈是线性表的一种应用,但只能在一端操作,遵循先进先出原则
例题:
答案:B
入栈和出栈要操作栈顶指针前移和后移,只有B中的链表要找到表尾结点需要遍历链表
链式栈出栈操作:
datatype Pop(slink *top)
{
if(LemptyStack(*top)) return -1;//栈空返回-1
datatype p =top->data;//获取栈顶元素
slink ptop = top;//存取栈顶结点
top=top->next;//栈顶指针后移
delete ptop;//释放栈顶结点
return p;//返回原栈顶元素
}
队列
队列先进先出,所以需要可以指向队首和队尾的指针
最适合做队列的链表为为带有队首、队尾指针的非循环单链表
最不适合做队列的链表为只带首指针的非循环双链表(因为这样的链表要找到队尾需要遍历链表,而且双链表插入删除更复杂,但对队列没用)
其实只要是只带一个指针、非循环的链表,都不适合做队列。
图
图由顶点集和边集构成,顶点集有穷非空
带权图成为网
邻接矩阵表示:
一维数组表示顶点,二维数组(邻接矩阵)表示边
邻接矩阵元素的值可以为0(代表不自身到自身无环),大于0的数(代表边的权值),∞(代表不邻接)
存储结构:
struct
{
char vexs[MAXVE]; //顶点集,字符型一维数组
int arc[MAXVEX]{MAXVEX];//邻接矩阵,整数型二维数组
int numNodes,numEdges; //图的顶点个数、边数
}MGraph//结构体名字
无向网图的创建:
void CreateGraph(MGraph *G)
{
int i,j,k,w;
cout<<"输入顶点数和边数"<<endl; //输入边数、点数
cin>>&G->numNodes>>&G->numEdges;
for(i=0;i<G->numNodes;i++) //初始化顶点集
cin>>G-vexs[i];
for(i=0;i<G->numNodes;i++) //初始化邻接矩阵(都为无穷)
for(j=0;j<G->numNodes;j++)
G->arc[i][j]=INFINITY;//无穷
for(k=0;k<G->numEdges;k++) //输入邻接矩阵
{
cout<<"输入边(vi,vj)的上标i,下标j,权值w\n"; //选定边,给定权值
cin>>i>>j>>w; //输入边的两个端点,输入权值
G->arc[i][j]=w; //将邻接矩阵对应位置赋值为权值
G->arc[j][i]=G->arc[i][j]; //无向图邻接矩阵对称
}
}
邻接表表示:
一个一维数组存储顶点集,从每个顶点甩出一个链表,表示该顶点的邻接点,
(1)顶点表:顺序表,每个结点由数据域和指针域表示,data存储顶点信息,firstedge指向边表第一个结点
(2)边表:链表,每个节点由邻接点域和指针域表示,adjvex存储某顶点邻接点在顶点表中的下标,next存储下一个邻接点的指针
如下图为一个无向图的邻接表结构:
有向图邻接表:(以顶点为弧尾,可以方便的找到顶点的出度,如果以顶点为弧头的话,叫逆邻接表)
带权值的有向网图:(为结点加一个weight域,存储其与前一个结点边的长度)
邻接表存储结构:
typedef char VertexType; /* 顶点类型应由用户定义 */
typedef int EdgeType; /* 边上的权值类型应由用户定义 */
typedef struct EdgeNode{ /* 边表结点 */
int adjvex; /*邻接点域,存储该顶点对应的下标 */
EdgeType info; /*用于存储权值,对于非网图可以不需要 */
struct EdgeNode *next; /* 链域,指向下一个邻接点 */
}EdgeNode;
typedef struct VertexNode{ /* 顶点表结点 */
VertexType data; /* 顶点域,存储顶点信息 */
EdgeNode *firstedge; /* 边表头指针 */
}VertexNode,AdjList [MAXVEX]; //数组存储结点表
typedef struct//图结构,包括顶点表、顶点数、边数
{
AdiList adjList;
int numNodes,numEdges;- /* 图中当前顶点数和边数 */
}GraphAdjList; //图
拓扑序列
有向图的拓扑序列就是图的广度优先遍历的一个应用。
- 若一个由图中所有点构成的序列 A 满足:对于图中的每条边 (x,y),x 在 A 中都出现在 y 之前,则称 A 是该图的一个拓扑序列。(起点在终点的前面)
- 拓扑序列是针对有向图,无向图是没有拓扑序列的。
- 有向无环图一定是拓扑序列,有向有环图一定不是拓扑序列。
排序方式:
把入度为0的点挨个删掉,删掉的点进入队列,入队顺序即为拓扑序列
实现思路
- 首先记录各个点的入度。
- 然后将入度为 0 的点放入队列。
- 将队列里的点依次出队列,然后找出所有出队列这个点发出的边,删除边,同时边的另一侧的点的入度 -1。
- 如果所有点都进过队列,则可以拓扑排序,输出所有顶点。否则输出 -1,代表不可以进行拓扑排序。
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, m;
int h[N], e[N], ne[N], idx;
int d[N];
int q[N];
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx;
idx ++;
}
//返回布尔序列是否存在, 若存在,则存储在q数组中
bool topsort()
{
int hh = 0;
int tt = -1;
//遍历每一个节点, 入度为零则入队
for (int i = 1; i <= n; i ++ )
{
if (!d[i])
{
tt ++;
q[tt] = i;
}
}
while (hh <= tt)
{
//队列不空,则取出头节点
int t = q[hh];
hh ++;
//遍历头节点的每一个出边
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (-- d[j] == 0)
{
tt ++;
q[tt] = j;
}
}
}
return tt == n - 1;
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 0; i < m; i ++ )
{
int a, b;
cin >> a >> b;
add(a, b);
d[b] ++ ;
}
if (!topsort())
{
puts("-1");
}
else
{
for (int i = 0; i < n; i ++ )
{
cout << q[i] << " ";
}
puts("");
}
system("pause");
return 0;
}
排序
快速排序
基本思想:
①设待排序序列的key集合k = {k1,k2,...ki,kj,...kn-1,kn}
②选取某个数x为基准,设法将x放到正确的位置,过程中可移动其他元素,经过一趟处理,是序列变为:x左侧元素全部<=x,x右侧元素全部>=x.
③对两侧的子序列重复步骤②,直至所有元素完成排序(递归)
算法:
typedef int keytype //设key为整型
//对子表R[low]...R[high]一趟快排
int qkpass(sqfile *F,int low ,int high) //sqfile是待排序的序列,不一定是数字,但其中的元素都有对应的key值,对key值进行排列
{
int i = low, j = high;
keytype x = F->R[low].; //取基准key,即左游标
F->R[0] = F->R[low]; //存入基准记录
while(i<j)
{
//从右到左扫描,如果比x大,向前移动j指针
while(i<j&&x<=F->R[j].key) j--;
//直到找到比x小的数,赋给左侧i指针位置,j指针前移
if(i<j){F->R[i] = F->R[j]; i++}
//从左到右扫描,如果比x小,向后移动i指针
while(i<j&&x>= F->R[i].key) i++;
//直到找到比x大的数,赋值给右侧j指针,i指针后移
if(i<j){F->R[j] = F->R[i]; j--;
}
//当i==j时,这趟排序结束
F->R[i] = F->R[0]; //基准记录存入第i位置
return i; //返回基准位置
}
//对F快排的递归算法
void QuickSort (sqfile *F,int low,int high)
{
int i;
low = 1; high =F->len; //low指向最左,high指向最右
if(low >=high) return; //递归结束条件,low>=high(一般不会出现low>high所以实际就是low=high)
i = qkpass(F,low,high); //对当前子表进行一趟快速排序
//递归调用排序
QuickSort(F,low,i-1); //右排序
QuickSort(F,i+1,hihg); //左排序
}
常用小函数:
tolower():大写字母转换成小写字母(一次只能操作一个字母)。
string类:重载了运算符+,-,=,可以直接比较和运算两个字符串,求长度:str.size()
代码debug
1.
2.expected unqualified-id beforexxx:使用了当前环境下未定义的符号
3.Left/Right operand of comma has no effect vs Right-hand operand of comma has no effect:左操作数无效,右操作数无效
4.warning: control reaches end of non-void function:函数缺少返回值
5.lvalue required as left operand of assignment:赋值语句使用不正确
6.
terminate called after throwing an instance of 'std::invalid_argument' what(): stoi 中断错误
错误位置:int t = stoi(arr[i]);,原因:arr[i]不可转化为整型。
7.terminate called after throwing an instance of 'std::logic_error' what(): basic_string::_M_construct null not valid:中断错误,定义了一个空字符串
8.
expected unqualified-id before numeric constant
是自己定义的枚举变量名与第三方库中的同名了,导致变量重复定义。
解决方法:
- 自己的类型加上命名空间
- 自定义的类型添加特定的前缀
9.segment fault常见原因:
段错误通常是由于解除引用一个未初始化或非法值的指针引起的。以发生频率为序,最终可能导致段错误的常见编程错误是:
1、坏指针错误:在指针赋值之前就用它来引用内存;或者向库函数传递一个坏指针(如果调试器显示系统程序中出现了段错误,很可能并不是系统程序引起的段错误,问题可能就出现在自己的代码中);或者指针被释放后还继续访问它的内容。
2、改写错误:越过数组边界写入数据,在动态分配的内存空间以外写入数据,或改写一些堆管理数据结构(在动态分配的内存之前的区域写入数据就很容易发生这种情况)。
3、指针释放引起的错误:释放同一块内存两次,或释放一块未曾使用malloc分类的内存,或释放一个无效的指针。一个极为常见的与释放内存有关的错误就是在 for(p=start;p;p=p->next) 这样的循环中迭代一个链表,并在循环体内使用 free(p) 这样的语句。这样,在下一次循环迭代时,程序就会对已经释放的指针进行解除引用操作,从而导致不可预料的结果。
10.顺序表也需要初始化,不要在定义结构时直接为定义数组,要定义一个指针,初始化时用new语句为数组指针赋值。顺便为表指针赋值