//只有自己才能看懂的云笔记,没有图的代码
线性表
一、顺序储存及其操作
数据类型:
struct List{
datatype data[MAXSIZE];
int last;
};
介绍:
考虑到线性表的插入、删除功能,即表长是可变的。因此,数组的容量要设计的足够大,用 datatype data[MAXSIZE] 表示,使用变量 last 记录当前线性表中最后一个元素在数组中的位置,即起到指针的作用。表空时last = -1.
- (1)线性表初始化:Init_List()
初始条件:表List不存在
操作结果:构造一个空表
SeqList * Init_List(SeqList *L)
{
L = (SeqList *)malloc(sizeof(SeqList));
if(NULL == L){
printf("动态内存分配失败!\n");
exit(-1);
}
else{
L->last = -1
return L;
}
}
(2)向指定位置插入元素:Insert_List()
顺序表完成这一运算则通过以下步骤进行:
01.将ai~an顺序向后移动,为新元素让出位置;
02.将x置入空出的第i个位置;
03.修改last指针(相当于修改表长),使之仍指向最后一个元素。
void Insert_List(SeqList *L, int i, datatype x)
{
int j;
if(L->last == MAXSIZE - 1){
printf("表已满,无法插入!\n");
return;
}
if(i < 1 || i > L->last + 2){
printf("输入的位置有误,无法插入!\n");
return;
}
//向后移动节点
for(j = L->last; j >= i-1; j--)
L->data[j+1] = L->data[j];
//插入新元素,指针移动到最后一个元素
L->data[i-1] = x;
L->last++;
return;
}
(3)删除指定位置的线性表记录:Delete_List()
顺序表完成这一操作的步骤如下:
01.将ai+1~an顺序向前移动;
02.修改last指针(相当于修改表长)使之仍指向最后一个元素。
注:定义变量 val 保存删除的元素
void delete_List(SeqList *L, int i, int *pval)
{
int j;
if(i < 1 || i > L->last+1){
printf("不存在第%d个元素!\n", i);
return;
}
else{
*pval = L->data[i-1];
for(j = i; j <= L->last; j++)
L->data[j-1] = L->data[j]
L->last--;
return;
}
}
(4)删除指定元素值的线性表记录
顺序表完成这一操作的步骤如下:
01.查找指定元素所在的位置
02.将ai+1~an顺序向前移动;
03.修改last指针(相当于修改表长)使之仍指向最后一个元素。
void delete_data_List(SeqList *L, datatype x)
{
int i, j;
if(-1 == L->last){
printf("表空!\n");
return;
}
//找到X元素后,跳出循环,得到元素的下标i;否则正常结束i=last+1
for(i = 0; i <= L->last; i++)
if(x == L->data[i])
break;
if(i == L->last + 1){
printf("未找到所查找的元素!\n");
return;
}
else{
for(j = i; j <= L->last; j++)
L->data[j] = L->data[j+1]
L->last--;
return;
}
}
注:可在(4)中调用函数(5)返回下标来实现。略。
(5)查找元素
在顺序表中完成该操作最简单的方法是:
从第一个元素起依次和x比较,直到找到一个与x相等的数据元素,则返回它在顺序表中的存储下标或序号(二者差一);或者查遍整个表都没有找到与x相等的元素,查找失败返回-1。
int Search_List(SeqList *L, datatype x)
{
int i = 0;
while(x != L->data[i] && i <= L->last)
i++;
if(i > L->last)
return -1;
return i;
}
(6)遍历输出
void print_List(SeqList *L)
{
int i = 0;
if(i > L->last)
printf("表空!\n");
while(i <= L->last){
printf("%d ",L->data[i]);
i++;
}
printf("\n");
return;
}
- main函数实现
#include<stdio.h>
#include<stdlib.h>
#define datatype int
#define MAXSIZE 50
typedef struct{
datatype data[MAXSIZE];
int last;
}SeqList;
int main()
{
SeqList *L;
L = Init_List(L);
Insert_List(L, 1, 10)
Insert_List(L, 1, 20)
print_List(L)
return 0;
}
二、单链表
- 数据类型及术语:
头指针、头结点、尾指针、尾结点等
typedef struct Node{
int data; //数据域
struct Node *pNext; //指针域 p->next
}NODE, *PNODE;
2.基础操作
(1)初始化链表Init_LinkList()
采用尾插法,输入创建结点的个数和元素:
PNODE Init_LinkList(void)
{
int len, i, val;
PNODE pHead = (PNODE)malloc(sizeof(NODE));
if ( NULL == pHead )
{
printf("分配失败,程序终止!\n");
exit(-1);
}
PNODE pTail = pHead;
pTail->pNext = NULL;
printf("请输入需要生成的链表节点的个数:len = ");
scanf_s("%d", &len);
for (i = 0; i < len; ++i)
{
printf("请输入第%d个节点的值:", i + 1);
scanf("%d", &val);
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if (NULL == pNew)
{
printf("分配失败,程序终止!\n");
exit(-1); //终止程序命令
}
pNew->date = val;
pTail->pNext = pNew;
pNew->pNext = NULL;
pTail = pNew;
}
return pHead;
}
(2)向链表指定位置插入元素insertItem(PNODE, int )
01.找到此位置(变量 des) 的前一个结点
02.指针操作(不清楚画示意图)
void insertItem(PNODE pHead, int des)
{
PNODE p = pHead;
int i = 0;
while(p && i < des - 1){
i++;
p = p->pNext
}
if(!p || i > des - 1)
printf("插入失败!\n");
else{
PNODE pNew = (PNODE)malloc(sizeof(Node));
if(NULL == pNew) exit(-1);
printf("请输入要插入的元素:");
scanf("%d", &(pNew->data));
pNew->next = p->next;
p->next = pNew;
}
return;
}
(3)向有序链表指定位置插入元素(头插法、尾插法)
尾插法见(1),头插法如下:
void insertHead(PNODE pHead, int x)
{
PNODE p = pHead;
PNODE pNew = (PNODE)malloc(sizeof(Node));
if(NULL == pNew) exit(-1);
pNew->data = x;
pNew->pNext = p->pNext;
p->pNext = pNew;
return;
}
(4)查找元素
01.按序号查找
算法思路:从链表的第一个元素结点起,判断当前结点是否是第i个,若是,则返回该结点的指针,否则继续查找下一个结点,直到表结束为止。若没有第i个结点,则返回空指针。
PNODE Get_LinkList(PNODE pHead, int i)
{
PNODE p = pHead;
int j = 0;
while(p->pNext != NULL && j < i){
p = p->pNext;
j++;
}
if(j == i)
return p;
else
return NULL;
}
02.按值查找
算法思路:从链表的第一个元素结点起,判断当前结点的值是否等于x,若是,则返回该结点的指针,否则继续查找下一结点,直到表结束为止。若没有找到值为x的结点,则返回空指针。
PNODE Locate_LinkList(PNODE pHead, int x)
{
PNODE p = pHead->pNext;
while(p != NULL && x != p->data){
p = p->pNext;
}
if(x == p->data)
return p;
else
return NULL;
}
(5)删除元素
算法思路:
① 找到第i-1个结点;若存在则继续步骤②,否则结束;
② 若存在第i个结点则继续步骤③,否则结束;
③ 删除第i个结点,结束。
void delete_LinkList(PNODE pHead, int i)
{
PNODE p, s;
p = Get_LinkList(pHead, i-1);
if(NULL == p)
return;
else if(NULL == p->pNext)
return;
else{
s = p->pNext;
p->pNext = s->pNext;
free(s)
}
return;
}
(6)遍历输出 treaverse_LinList( )
void traverse_Linklist(PNODE pHead)
{
PNODE p = pHead->pNext;
while(NULL != p)
{
printf("%d ", p->date);
p = p->pNext;
}
printf("\n");
return;
}
3.main函数
#include<stdio.h>
#include<stdlib.h>
typedef struct Node{
int data; //数据域
struct Node *pNext; //指针域 p->next
}NODE, *PNODE;
int main()
{
PNODE pHead = NULL;
pHead = Init_List();
return 0;
}
栈和队列
一、栈的基本操作
介绍:
栈(Stack)是限制在表的一端进行插入和删除操作的线性表。允许进行插入、删除操作的这一端称为栈顶(Top),另一个固定端称为栈底(bottom)。当表中没有元素时称为空栈。先入后出的一种实现方式?栈!
函数的递归,就是利用栈来实现的。
链接里的文章,作者叙述的非常清晰!
理解递归的本质:递归与栈
1.顺序栈(静态栈),用数组来实现:
注:数组实现,类似于顺序储存,初始化top = -1,栈为空,增加元素top++。
基本操作:
数据类型
typedef struct stack{
int data[50];
int top;
}SeqStack;
(1)初始化栈
void Init(SeqStack *ps)
{
SeqStack *s;
ps = (SeqStack *)malloc(sizeof(SeqStack));
if(NULL == s) exit(-1);
ps->top = -1;
return;
}
(2)判断栈为空:空返回1,否则0
int Is_empty(SeqStack *ps)
{
if(ps->top == -1)
return 1;
else
return 0;
}
(3)入栈:需要判断栈是否已满
void push(SeqStack *ps, int x)
{
if(ps->top == 49) //最多有50个元素,最大的下标为49
return;
else{
ps->top++;
ps->data[ps->top] = x;
return;
}
}
(4)出栈
void pop(SeqStack *ps, int *x)
{
if(Is_empty(ps)) //最多有50个元素,最大的下标为49
return;
else{
*x = ps->data[ps->top];
ps->top--;
return;
}
}
2.动态栈用链表实现
链栈,类似于链表,建立第一个头结点(便于操作,不存放有效数据,),top 和 bottom 分别指向它,初始化bottom = top,栈为空;压栈后,新的结点数据域指向top(建立联系),再把 top 指针指向新结点本身,压栈成功。
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
//用两个结构体来定义栈的结构
typedef struct Node
{
int data;
struct Node *pNext;
}NODE, *PNODE;
typedef struct stack
{
PNODE top;
PNODE bottom;
}STACK,*PSTACK;
void init(PSTACK); //初始化栈
void push(PSTACK, int); //压栈
bool empty(PSTACK); //判断栈是否为空
bool pop(PSTACK, int *); //出栈
void traverse(PSTACK); //遍历输出
void clear(PSTACK); //清空栈
int main()
{
STACK S;
int val;
init(&S);
push(&S, 1);
push(&S, 2);
push(&S, 3);
push(&S, 4);
push(&S, 5);
traverse(&S);
if (pop(&S, &val))
printf("出栈成功,出栈的元素是:%d\n", val);
else
printf("出栈失败!\n");
traverse(&S);
clear(&S);
return 0;
}
void init(PSTACK pS)
{
pS->top = (PNODE)malloc(sizeof(NODE));
if (pS->top == NULL)
{
printf("动态内存分配失败!\n");
exit(-1);
}
else
{
pS->bottom = pS->top;
pS->top->pNext = NULL;
}
return;
}
void push(PSTACK pS, int x)
{
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if (NULL == pNew)
{
printf("动态内存分配失败!\n");
exit(-1);
}
else
{
pNew->pNext = pS->top;
pNew->data = x;
pS->top = pNew;
}
return;
}
bool empty(PSTACK pS)
{
if (pS->top == pS->bottom)
return true;
else
return false;
}
bool pop(PSTACK pS, int *pVal)
{
if (empty(pS))
{
return false;
}
else
{
PNODE r = pS->top;
*pVal = r->data;
pS->top = pS->top->pNext;
free(r);
r = NULL;
return true;
}
}
void traverse(PSTACK pS)
{
PNODE p = pS->top;
while (p != pS->bottom)
{
printf("%d ", p->data);
p = p->pNext;
}
printf("\n");
return;
}
void clear(PSTACK pS)
{
PNODE p = pS->top;
PNODE q = p->pNext;
while (p != pS->bottom)
{
q = p->pNext;
free(p);
p = q;
}
pS->top = pS->bottom;
return;
}
二、队列
插入在表一端进行,而删除在表的另一端进行,这种数据结构被称为队或队列(Queue),允许插入的一端称为队尾(rear),允许删除的一端称为队头(front)。
**注:**为解决队满和队空判断条件问题,采用少用一个元素空间的方法:(rear + 1) % MAXSIZE == front 判断队满
1.循环队列—数组(图片不符)
#include<stdio.h>
//空出一个不用的位置
typedef struct queue
{
int data[5];
int front;
int rear;
}SeQueue;
void init(SeQueue *);
void enter(SeQueue *, int);
void delete(SeQueue *, int*);
void traversal(SeQueue *);
int main()
{
SeQueue Q;
init(&Q);
enter(&Q, 1);
enter(&Q, 2);
enter(&Q, 3);
enter(&Q, 4);
enter(&Q, 5);
traversal(&Q);
return 0;
}
void init(SeQueue *pQ)
{
pQ->front = 0;
pQ->rear = 0;
return;
}
void enter(SeQueue *pQ, int x)
{
if ((pQ->rear + 1) % 5 == pQ->front)
printf("队列已满,无法添加!\n");
pQ->data[pQ->rear] = x;
pQ->rear = (pQ->rear + 1) % 5;
return;
}
void delete(SeQueue *pQ, int* px)
{
if (pQ->front == pQ->rear)
printf("队列为空,无法删除!");
*px = pQ->data[pQ->front];
pQ->front = (pQ->front + 1) % 5;
return;
}
void traversal(SeQueue *pQ)
{
int i = pQ->front;
while (i != pQ->rear)
{
printf("%d ", pQ->data[i]);
i = (i + 1) % 5;
}
printf("\n");
return;
}
2.循环队列—链表
算法思想:
构建两个结构体,一个存放结点,一个存放队列的指针。
01.初始化,创建一个头结点(不存数据,便于操作),指针域为NULL,使队列的 front 和 rear 分别指向该节点。
02.入队:操作 rear 指针。继续创建结点存放数据,指针域为NULL,把上个结点的指针域指向该结点,建立关联,然后把 rear 指针指向该结点,完成入队
03.出队:操作 front 指针。首先,判断队列是否为空。然后,创建一个指向待删除结点的指针p,把 p->data 存放,把 front 指针指向结点的指针域指向待删除结点的下一个结点,完成出队。最后,释放删除的结点。
04.遍历:创建指向front的指针p,只要该指针指向结点的指针域非空,循环p = p->pNext
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<stdbool.h>
typedef struct Node
{
int data;
struct Node *pNext;
}NODE, *PNODE;
typedef struct queue
{
PNODE front;
PNODE rear;
}QUEUE, *pQueue;
void init(pQueue);
void enter(pQueue, int);
bool delete(pQueue, int *);
void traverse(pQueue);
int main()
{
QUEUE Q;
int val; //val用来保存出队的元素
init(&Q);
enter(&Q, 1);
enter(&Q, 2);
enter(&Q, 3);
traverse(&Q);
if (delete(&Q, &val))
printf("出队成功,出队的元素是:%d\n", val);
if (delete(&Q, &val))
printf("出队成功,出队的元素是:%d\n", val);
traverse(&Q);
return 0;
}
void init(pQueue que)
{
PNODE q = (PNODE)malloc(sizeof(NODE));
if (q == NULL)
{
printf("动态内存创建失败!\n");
exit(-1);
}
q->pNext = NULL;
que->front = q;
que->rear = que->front;
return;
}
void enter(pQueue pQ, int val)
{
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if (pNew == NULL)
{
printf("动态内存创建失败!\n");
exit(-1);
}
pNew->data = val;
pNew->pNext = NULL;
pQ->rear->pNext = pNew;
pQ->rear = pNew;
return;
}
bool delete(pQueue pQ, int *pVal)
{
if (pQ->rear == pQ->front)
{
printf("队列为空,删除失败!\n");
return false;
}
else
{
PNODE p = pQ->front->pNext;
*pVal = p->data;
pQ->front->pNext = p->pNext;
if (p == pQ->rear)
pQ->rear = pQ->front;
free(p);
return true;
}
}
void traverse(pQueue pQ)
{
PNODE r;
r = pQ->front;
while (r->pNext != NULL)
{
printf("%d ", r->pNext->data);
r = r->pNext;
}
printf("\n");
return;
}
树和图
一、树
相关术语:
结点类型:根结点、孩子结点、双亲结点、叶子结点
度、层次、深度、森林等。
二叉树: 重点掌握
1.二叉树的顺序储存
完全二叉树和满二叉树采用顺序存储比较合适,树中结点的序号可以唯一地反映出结点之间的逻辑关系,这样既能最大限度地节省存储空间,又可以利用数组元素的下标值确定结点在二叉树中的位置及结点之间的关系
数据类型
#define MAXNODE 50
#define datatype char
typedef datatype SqBiTree[MAXNODE];
SqBiTree bt; //bt定义为含有MAXNODE个char类型元素的一维数组
2.二叉树的链式储存
数据类型
typedef struct BitNode{
char data;
struct BitNode *lchild, *rchild;
}BiTree,*pBiTree;
(1)初始化
pBiTree tree = NULL;
(2)创建树
创建二叉树时需要使用二级指针,否则递归调用函数时不能把malloc的结点相连,还会造成内存泄漏,原因画图。或者使用返回指针的方式。
//先序输入各结点,#代表空
void create(pBiTree *ptree)
{
char ch;
scanf_s("%c",&ch);
if(ch == '#')
*ptree = NULL;
else
{
*ptree = (pBiTree)malloc(sizeof(BiTree));
if(NULL == *ptree)
{
printf("动态内存分配失败!\n");
exit(-1);
}
(*ptree)->data = ch;
create(&(*ptree)->lchild);
create(&(*ptree)->rchild);
}
return;
}
(3)删除结点
分析:
情况1 删除节点为叶子节点
情况2 删除节点有一个孩子
情况3 删除节点有两个孩子er
不论哪种情况,我们首先要做的是找到要删除的节点(current)及其父节点(parent)。
(4)查找结点
给定要查找的元素,返回该结点的指针。利用递归,终止条件为该树的左右结点指向为NULL
pBiTree Point(pBiTree T, char E)//查找元素值为e的结点的指针
{
pBiTree ret = NULL;
if (T != NULL)
{
if (T->data == E)
ret = T;
else
{
if (ret == NULL)
ret = Point(T->lchild, E);
if (ret == NULL)
ret = Point(T->rchild, E);
}
}
return ret;
}
(5)遍历:
输出、查找、求树的深度、叶子结点等操作都是建立在遍历的基础上进行的。
1、先序遍历
先遍历根结点
再先序遍历左结点
再先序遍历右结点
2、中序遍历
先中序遍历左节点
再遍历根结点
再中序遍历右节点
3、后序遍历:
先中序遍历左节点
再中序遍历右节点
再遍历根结点
4.层次遍历:
每层从左到右遍历结点
//先序遍历,中、后遍历交换次序即可
void traverse(pBiTree ptree)
{
if(ptree == NULL)
return;
else
{
printf("%c",ptree->data);
traverse(ptree->lchild);
traverse(ptree->rchild);
}
}
利用数组实现出队、入队,完成层次遍历
void LevelOrderTree(BSTNode *Root)
{
BSTNode *queue[20] = { NULL }; //构造足够大的队列存放结点
int front = 0, rear = 0;
if (Root == NULL)
return;
rear = 1;
queue[rear] = Root; //空出下标0的位置,根节点入队queue[1]
while (front != rear) //front = rear 队列为空,结束循环
{
front++;
printf("%d ",queue[front]->key); //出队,访问数据
if (queue[front]->lchild != NULL)
{
rear++;
queue[rear] = queue[front]->lchild; //根节点的左孩子入队
}
if (queue[front]->rchild != NULL)
{
rear++;
queue[rear] = queue[front]->rchild; //根节点的右孩子入队
}
}
return;
}
3.二叉排序树
作者:月云指霄
实现增、删、查、找功能
思路十分清晰,注意二级指针的使用及3处小错误!
二、图
1.图的定义
图(Graph)是由顶点的有穷非空集合和顶点之间的边集合组成,通常表示为:G(V,E),图中的数据元素称为顶点(Vertex),不允许没有顶点,任意两个顶点间都有可能有关系,顶点间的逻辑关系用边(edge)来表示。
2.图的分类
01.无向图:(Vi,Vj)
02.有向图: <Vi,Vj> i是尾,j是头
03.简单图
04.稀疏图/稠密图。是相对而言的 n*LOGn
05.网
带权的图
图的边或弧具有与它相关的数字,这种图的边或弧相关的数叫做权
3.顶点与边的关系
邻接点
相关联/邻接
顶点V的度
最优路径
回路/环
连通图/强连通图
连通分量
极大顶点数
4.图的储存结构
01.邻接矩阵
02.邻接表/逆邻接表
03.十字链表