第一章:绪论

  • 1.1数据结构的基本概念
  • 1.2数据结构的三要素
  • 1.3算法的基本概念
  • 1.4算法的时间复杂度​

第二章:线性表

  • 2.1线性表的定义
  • 2.2顺序表的定义
  • 2.2顺序表的基本操作
  • 2.3线性表的链式表示

第三章:栈和队列

  • 3.1栈
  • 3.2队列
  • 3.3栈的应用
  • 3.4特殊矩阵的压缩存储

第四章:串

  • 4.1串的定义和实现
  • 4.2串的模式匹配

第五章:树

  • 5.1树的基本概念
  • 5.2二叉树的概念
  • 5.3二叉树的遍历和线索二叉树
  • 5.4树与二叉树的应用

第六章 排序

  • 6.1排序的基本概念
  • 6.2插入排序
  • 6.3交换排序
  • 6.4选择排序
  • 6.5归并排序
  • 6.6基数排序
下面开始本节的内容

第一章:绪论

1.1数据结构的基本概念

1.数据:数据是信息的载体,是描述客观事实属性的数,字符以及所有能输入到计算机中并被程序识别和处理符号的集合。

2.数据元素:数据元素是数据的基本单位,作为一个整体进行考虑处理,一个数据元素可由若干数据项组成,数据项是构成数据元素的不可分割的最小单位。

3.数据对象:数据对象具有相同性的数据元素的集合,是数据的一个子集。

4.数据类型:数据类型是一个值的集合和定义在此集合上一组操作的总称。

5.数据结构:数据结构是相互之间存在一种或者多种特定关系的数据元素的集合。

1.2数据结构的三要素

1.数据的逻辑结构:

逻辑结构指数据元素之间逻辑关系,从逻辑关系上描述数据。

逻辑结构:

集合结构,线性结构,树形结构,图状结构。

  1. 集合结构:结构中的数据元素之间除“同属一个集合”外,别无其它关系。
  2. 线性结构:结构中的数据元素之间只存在一对一的关系,除了第一个元素,所有元素都有唯一前驱;除了最后一个元素,所有元素都有唯一后继。
  3. 树形结构:结构中数据元素之间存在一对多的关系。
  4. 图状结构:数据元素之间是多对多的关系。

2.数据的存储结构(物理结构):

存储结构是指数据结构在计算机中表示(映像),也叫物理结构。

存储结构:

顺序存储,链式存储,索引存储,散列存储。

顺序存储:把逻辑上相邻的元素存储在物理位置也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。

链式存储:逻辑上相邻的元素在物理位置上可以不相邻,借助指示元素存储地址的指针来表示元素之间的逻辑关系。

索引存储:在存储元素信息的同时,还建立附加的索引表,索引表中的每项称为索引项,索引项的一般形式是(关键字,地址)

散列存储:根据元素的关键字直接计算出该元素的存储地址,又称哈希(Hash)存储。

3.数据的运算

在数据上的运算包括运算的定义,运算的定义针对逻辑结构,支出运算的功能,运算的实现针对存储结构,支出运算的具体操作步骤。

1.3算法的基本概念

程序=算法+数据结构

算法:一系列的计算步骤,用来将输入数据转化成输出结构。

算法的特征:

有穷性,确定性,可行性,输入,输出。

1.4算法的时间复杂度

算法中基本操作重复执行的次数是问题规模n的某个函数f(n),算法的时间量度记作T(n)=O(n),它表示随问题规模n的增大而增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称时间复杂度。

1.5算法的空间复杂度

算法的空间复杂度S(n)定义为该算法所耗费的存储空间,它是问题规模n的函数。记为S(n)=O(g(n))。

第二章:线性表

2.1线性表的定义

线性表具有相同数据类型的n(n>0)个数据元素的优先序列,其中n为表长,n=0,时线性表是一个空表。

2.2顺序表的定义

1.动态分配

//顺序表的实现——动态分配
#include<stdio.h>
#include<stdlib.h>//malloc、free函数的头文件
#define InitSize 10 //默认的最大长度
typedef struct{
int *data;//指示动态分配数组的指针
int MaxSize; //顺序表的最大容量
int length; //顺序表的当前长度
}SeqList;
//初始化
void InitList(SeqList &L){
//用malloc 函数申请一片连续的存储空间
L.data =(int*)malloc(InitSize*sizeof(int)) ;
L.length=0;
L.MaxSize=InitSize;
}
//增加动态数组的长度
void IncreaseSize(SeqList &L,int len){
int *p=L.data;
L.data=(int*)malloc((L.MaxSize+len)*sizeof(int));
for(int i=0;i<L.length;i++){
L.data[i]=p[i]; //将数据复制到新区域
}
L.MaxSize=L.MaxSize+len; //顺序表最大长度增加len
free(p); //释放原来的内存空间

}
int main(void){
SeqList L; //声明一个顺序表
InitList(L);//初始化顺序表
IncreaseSize(L,5);
return 0;
}

2.静态分配:

//顺序表的实现--静态分配

#include<stdio.h>
#define MaxSize 10 //定义表的最大长度
typedef struct{
int data[MaxSize];//用静态的"数组"存放数据元素
int length; //顺序表的当前长度
}SqList; //顺序表的类型定义(静态分配方式)
void InitList(SqList &L){
for(int i=0;i<MaxSize;i++){
L.data[i]=0; //将所有数据元素设置为默认初始值

}
L.length=0;
}
int main(){
SqList L;//声明一个顺序表
InitList(L);//初始化一个顺序表
for(int i=0;i<MaxSize;i++){
printf("data[%d]=%d\n",i,L.data[i]);
}
return 0;
}

特点:

随机访问,可以在O(1)时间内找到第i个元素

存储密度高,每个节点存储数据元素。

2.2顺序表的基本操作

1.插入操作:平均时间复杂度O(n)

bool ListInsert(SqList &L, int i, int e){ 
//判断i的范围是否有效
if(i<1||i>L.length+1)
return false;
if(L.length>MaxSize) //当前存储空间已满,不能插入
return false;

for(int j=L.length; j>i; j--){ //将第i个元素及其之后的元素后移
L.data[j]=L.data[j-1];
}
L.data[i-1]=e; //在位置i处放入e
L.length++; //长度加1
return true;
}

2.删除操作:平均时间复杂度O(n)

bool LisDelete(SqList &L, int i, int &e){ // e用引用型参数 
//判断i的范围是否有效
if(i<1||i>L.length)
return false;

e = L.data[i-1] //将被删除的元素赋值给e

for(int j=L.length; j>i; j--){ //将第i个后的元素前移
L.data[j-1]=L.data[j];
}
L.length--; //长度减1
return true;
}

3.按位查找(获取l表中第i个值):平均复杂度位o(1)

#define MaxSize 10            //定义最大长度 
typedef struct{
ElemType data[MaxSize]; //用静态的“数组”存放数据元素
int Length; //顺序表的当前长度
}SqList; //顺序表的类型定义

ElemType GetElem(SqList L, int i){
// ...判断i的值是否合法
return L.data[i-1]; //注意是i-1
}

4.按值查找:时间复杂度哦o(n)

#define InitSize 10            //定义最大长度 
typedef struct{
ElemTyp *data; //用静态的“数组”存放数据元素
int Length; //顺序表的当前长度
}SqList;

//在顺序表L中查找第一个元素值等于e的元素,并返回其位序
int LocateElem(SqList L, ElemType e){
for(int i=0; i<L.lengthl i++)
if(L.data[i] == e)
return i+1; //数组下标为i的元素值等于e,返回其位序i+1
return 0; //推出循环,说明查找失败
}

2.3线性表的链式表示

2.3.1单链表的定义

定义:线性表的链式存储方式成为单链表,通过一组任意的存储单元来存储线性表中的数据元素。

typedef struct LNode{//定义单链表结点类型
ElemType data; //数据域
struct LNode *next;//指针域
}LNode, *LinkList;

1.不带结点的单链表

```bash
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkList;

//初始化一个空的单链表
bool InitList(LinkList &L){ //注意用引用 &
L = NULL; //空表,暂时还没有任何结点;
return true;
}

void test(){
LinkList L; //声明一个指向单链表的指针: 头指针
//初始化一个空表
InitList(L);
//...
}

//判断单链表是否为空
bool Empty(LinkList L){
if (L == NULL)
return true;
else
return false;
}

2.带结点的单链表

typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkList;

//初始化一个单链表(带头结点)
bool InitList(LinkList &L){
L = (LNode*) malloc(sizeof(LNode)); //头指针指向的结点——分配一个头结点(不存储数据)
if (L == NULL) //内存不足,分配失败
return false;
L -> next = NULL; //头结点之后暂时还没有结点
return true;
}

void test(){
LinkList L; //声明一个指向单链表的指针: 头指针
//初始化一个空表
InitList(L);
//...
}

//判断单链表是否为空(带头结点)
bool Empty(LinkList L){
if (L->next == NULL)
return true;
else
return false;
}


C语言数据结构_结点

第三章:栈和队列

3.1栈

3.1.1栈的基本概念

定义:栈是特殊的线性表,只允许一端进入插入或者删除,其逻辑结构与普通线性表相同

3.1.2栈的基本操作

创建和销毁,增加和删除,查找与其他。

*3.1.3栈的链式存储

1.定义:采用链式存储的栈称为链栈。

2.优点:链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。

3.特点:

进栈和出栈都只能在栈顶一端进行(链头作为栈顶)

链表的头部作为栈顶,意味着:

1. 在实现数据"入栈"操作时,需要将数据从链表的头部插入;

2. 在实现数据"出栈"操作时,需要删除链表头部的首元节点;

因此,链栈实际上就是一个只能采用头插法插入或删除数据的链表;

栈的链式存储结构可描述为:

typedef struct Linknode{
ElemType data; //数据域
struct Linknode *next; //指针域
}*LiStack; //栈类型的定义

4.栈的基本操作

初始化,出栈,进栈,获取栈顶的元素,判空,判满。

带头结点的链栈基本操作

#include<stdio.h>

struct Linknode{
int data; //数据域
Linknode *next; //指针域
}Linknode,*LiStack;

typedef Linknode *Node; //结点结构体指针变量
typedef Node List; //结点结构体头指针变量

//1. 初始化
void InitStack(LiStack &L){ //L为头指针
L = new Linknode;
L->next = NULL;
}

//2.判栈空
bool isEmpty(LiStack &L){
if(L->next == NULL){
return true;
}
else
return false;
}

//3. 进栈(:链栈基本上不会出现栈满的情况)
void pushStack(LiStack &L, int x){
Linknode s; //创建存储新元素的结点
s = new Linknode;
s->data = x;

//头插法
s->next = L->next;
L->next = s;
}

//4.出栈
bool popStack(LiStack &L, int &x){
Linknode s;
if(L->next == NULL) //栈空不能出栈
return false;

s = L->next;
x = s->data;
L->next = L->next->next;
delete(s);

return true;
}

不带头结点的链栈基本操作

#include<stdio.h>

struct Linknode{
int data; //数据域
Linknode *next; //指针域
}Linknode,*LiStack;

typedef Linknode *Node; //结点结构体指针变量
typedef Node List; //结点结构体头指针变量

//1.初始化
void initStack(LiStack &L){
L=NULL;
}

//2.判栈空
bool isEmpty(LiStack &L){
if(L == NULL)
return true;
else
teturn false;
}

//3.进栈
void pushStack(LiStack &L, int x){
Linknode s; //创建存储新元素的结点
s = new Linknode;

s->next = L;
L = s;
}

//4.出栈
bool popStack(LiStack &L, int &x){
Linknode s;
if(L = NULL) //栈空不出栈
return false;

s = L;
x = s->data;
L = L->next;
delete(s);

return true;
}

3.2队列

3.2.1队列的基本操作

定义:队列简称队,是一种操作受限的线性表,只允许在表的一端进行插入,在表的另一端删除。

特点:

队列是操作受限的线性表,只允许在一端进行插入 (入队),另一端进行删除 (出队)

操作特性:先进先出 FIFO

队头:允许删除的一端

队尾:允许插入的一端

空队列:不含任何元素的空表

队列的基本操作

创建与销毁,增加与删除,查找与其他

3.2.2队列的顺序存储结构

对头指针:指向队头元素

队尾指针:指向队尾元素的下一个位置

1.队列存储的基本操作

//队列的顺序存储类型
# define MaxSize 10; //定义队列中元素的最大个数
typedef struct{
ElemType data[MaxSize]; //用静态数组存放队列元素
//连续的存储空间,大小为——MaxSize*sizeof(ElemType)
int front, rear; //队头指针和队尾指针
}SqQueue;

//初始化队列
void InitQueue(SqQueue &Q){
//初始化时,队头、队尾指针指向0
Q.rear = Q.front = 0;
}

void test{
SqQueue Q; //声明一个队列
InitQueue(Q);
//...
}

// 判空
bool QueueEmpty(SqQueue 0){
if(Q.rear == Q.front) //判空条件后
return true;
else
return false;
}

2.循环队列

a%b == a除以b的余数

初始:Q.front = Q.rear = 0;

队首指针进1:Q.front = (Q.front + 1) % MaxSize

队尾指针进1:Q.rear = (Q.rear + 1) % MaxSize —— 队尾指针后移,当移到最后一个后,下次移动会到第一个位置

队列长度:(Q.rear + MaxSize - Q.front) % MaxSize

3.2.3队列的链式存储结构

1.定义:队列的链式表示链队列,实际上是一个同时带有队头指针和队尾指针的单链表。

链队列:用链表表示队列,限制仅在表头删除和表尾插入的单链表。

typedef struct LinkNode{      //链式队列结点
ElemType data;
struct LinkNode *next;
}

typedef struct{ //链式队列
LinkNode *front, *rear; //队列的队头和队尾指针
}LinkQueue;

2.带头结点

初始化与判空

void InitQueue(LinkQueue &Q){
//初始化时,front、rear都指向头结点
Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode));
Q.front -> next = NULL;
}

//判断队列是否为空
bool IsEmpty(LinkQueue Q){
if(Q.front == Q.rear) //也可用 Q.front -> next == NULL
return true;
else
return false;
}

入队操作

//新元素入队 (表尾进行)
void EnQueue(LinkQueue &Q, ElemType x){
LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode)); //申请一个新结点
s->data = x;
s->next = NULL; //s作为最后一个结点,指针域指向NULL
Q.rear->next = s; //新结点插入到当前的rear之后
Q.rear = s; //表尾指针指向新的表尾
}

出队操作

//队头元素出队
bool DeQueue(LinkQueue &Q, ElemType &x){
if(Q.front == Q.rear)
return false; //空队

LinkNode *p = Q.front->next; //p指针指向即将删除的结点 (头结点所指向的结点)
x = p->data;
Q.front->next = p->next; //修改头结点的next指针
if(Q.rear == p) //此次是最后一个结点出队
Q.rear = Q.front; //修改rear指针
free(p); //释放结点空间

return true;
}

队列满的条件

顺序存储:预分配存储空间

链式存储:一般不会队满,除非内存不足

计算链队长度 (遍历链队)

设置一个记录链式队列长度

初始化 & 判空

入队操作

//新元素入队 (表尾进行)
void EnQueue(LinkQueue &Q, ElemType x){
LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode)); //申请一个新结点
s->data = x;
s->next = NULL;

//第一个元素入队时需要特别处理
if(Q.front = NULL){ //在空队列中插入第一个元素
Q.front = s; //修改队头队尾指针
Q.rear = s;
}else{
Q.rear->next = s; //新结点插入到rear结点之后
Q.rear = s; //修改rear指针指向新的表尾结点
}
}

3.3栈的应用

3.1.1栈在括号匹配中的应用

#define MaxSize 10   

typedef struct{
char data[MaxSize];
int top;
} SqStack;

//初始化栈
InitStack(SqStack &S)

//判断栈是否为空
bool StackEmpty(SqStack &S)

//新元素入栈
bool Push(SqStack &S, char x)

//栈顶元素出栈,用x返回
bool Pop(SqStack &S, char &x)



bool bracketCheck(char str[], int length){
SqStack S; //声明
InitStack(S); //初始化栈

for(int i=0; i<length; i++){
if(str[i] == '(' || str[i] == '[' || str[i] == '{'){
Push(S, str[i]); //扫描到左括号,入栈
}else{
if(StackEmpty(S)) //扫描到右括号,且当前栈空
return false; //匹配失败

char topElem; //存储栈顶元素
Pop(S, topElem); //栈顶元素出栈

if(str[i] == ')' && topElem != '(' )
return false;

if(str[i] == ']' && topElem != '[' )
return false;

if(str[i] == '}' && topElem != '{' )
return false;
}
}

StackEmpty(S); //栈空说明匹配成功
}

3.3.2栈的表达式求值的应用

#define MaxSize 10   

typedef struct{
char data[MaxSize];
int top;
} SqStack;

//初始化栈
InitStack(SqStack &S)

//判断栈是否为空
bool StackEmpty(SqStack &S)

//新元素入栈
bool Push(SqStack &S, char x)

//栈顶元素出栈,用x返回
bool Pop(SqStack &S, char &x)



bool bracketCheck(char str[], int length){
SqStack S; //声明
InitStack(S); //初始化栈

for(int i=0; i<length; i++){
if(str[i] == '(' || str[i] == '[' || str[i] == '{'){
Push(S, str[i]); //扫描到左括号,入栈
}else{
if(StackEmpty(S)) //扫描到右括号,且当前栈空
return false; //匹配失败

char topElem; //存储栈顶元素
Pop(S, topElem); //栈顶元素出栈

if(str[i] == ')' && topElem != '(' )
return false;

if(str[i] == ']' && topElem != '[' )
return false;

if(str[i] == '}' && topElem != '{' )
return false;
}
}

StackEmpty(S); //栈空说明匹配成功
}

1.中缀表达式

① a + b
② a + b - c
③ a + b - c*d
④ ((15 ÷ (7-(1+1)))×3)-(2+(1+1))
⑤ A + B × (C - D) - E ÷ F

2.后缀表达式

① a b +
② ab+ c - / a bc- +
③ ab+ cd* -
④ 15 7 1 1 + - ÷ 3 × 2 1 1 + + -
⑤ A B C D - × + E F ÷ - (机算结果)
A B C D - × E F ÷ - + (不选择)

3.前缀表达式

运算符在两个操作数前面

① + a b
② - +ab c
③ - +ab *cd

4.中缀表达式的计算

两个算法的结合: 中缀转后缀 + 后缀表达式的求值

初始化两个栈,操作数栈 和运算符栈

若扫描到操作数,压人操作数栈

3.4特殊矩阵的压缩存储

1.一维数组

Elemtype a[10];

2.二维数组

Elemtype b[2][4]; //2行4列的二维数组

3.4.1特殊矩阵的存储

1.对称矩阵

C语言数据结构_数据_02

C语言数据结构_结点_03

对称矩阵

2.三角矩阵

以主对角线划分,三角矩阵有上(下)三角两种。上(下)三角矩阵的下(上)三角(不含主对角线)中的元素均为常数。在大多数情况下,三角矩阵常数为零。

C语言数据结构_二叉树_04

3.三角矩阵

C语言数据结构_二叉树_05

第四章:串

4.1串的定义和实现

4.1.1串的定义

串: 零个或多个字符组成的有限序列,如 S = 'iPhone 11 Pro Max?';

串名:S是串名;

串的长度:串中字符的个数n;

空串:n=0时的串;

子串:串中任意多个连续的字符组成的子序列称为该串的子串;

主串:包含子串的串;

字符在主串中的位置:某个字符在串中的序号(从1开始);

子串在主串中的位置:子串的第一个字符在主串中的位置;

空串 V.S 空格串:

M = ‘’ 是空串;

N = ’ ’ 是空格串;

串 V.S 线性表:

串是特殊的线性表,数据元素之间呈线性关系(逻辑结构相似);

串的数据对象限定为字符集:中文字符、英文字符、数字字符、标点字符…

串的基本操作,如增删改除通常以子串为操作对象

4.1.2串的存储结构

1.长顺序存储

#define MAXLEN 255   //预定义最大串长为255

typedef struct{
char ch[MAXLEN]; //静态数组实现(定长顺序存储)
//每个分量存储一个字符
//每个char字符占1B
int length; //串的实际长度
}SString;

2.堆分配存储表示

//动态数组实现
typedef struct{
char *ch; //按串长分配存储区,ch指向串的基地址
int length; //串的长度
}HString;

HString S;
S.ch = (char *) malloc(MAXLINE * sizeof(char)); //基地址指针指向连续空间的起始位置
//malloc()需要手动free()
S.length;

3.串的链式存储

typedef struct StringNode{
char ch; //每个结点存1个字符
struct StringNode *next;
}StringNode, * String;

4.2串的模式匹配

模式匹配:子串的定位操作称为串的模式,求的是子串在主串的位置。

4.2.1朴素模式匹配算法

int Index(SString S, SString T){
int i=1; //扫描主串S
int j=1; //扫描模式串T
while(i<=S.length && j<=T.length){
if(S.ch[i] == T.ch[j]){
++i;
++j; //继续比较后继字符
}
else{
i = i-j+2;
j=1; //指针后退重新开始匹配
}
}
if(j>T.length)
return i-T.length;
else
return 0;
}

4.2.2KMP算法

利用next数组进行模式匹配

int Index_KMP(SString S, SString T, int next[]){
int i=1; //主串
int j=1; //模式串
while(i<S.length && j<=T.length){
if(j==0 || S.ch[i]==T.ch[j]){ //第一个元素匹配失败时
++j;
++i; //继续比较后继字符
}
else
j=next[j] //模式串向右移动
}
if(j>T.length)
return i-T.length; //匹配成功
}

3.时间复杂度

next数组时间复杂度=O(M)

最坏时间复杂度O=(N)

KMP算法的最坏复杂度O(m+n)

第五章:树

5.1树的基本概念

5.1.1树的定义

树是n个结点的有限集。

空树:n=0

根结点、分支结点、叶子结点

非空树的特性

子树

5.1.2基本术语

结点之间的关系描述
路径、路径长度
结点、树的属性描述
1. 结点的层次(深度)——从上往下
2. 结点的高度——从下往上
3. 树的高度——总共多少层
4. 结点的度——有几个孩子
5. 树的度——各结点的度的最大值
有序树、无序树
森林

5.1.3树的性质

C语言数据结构_二叉树_06

5.2二叉树的概念

5.2.1二叉树的定义与特性

定义

二叉树是n个结点的有限集,或者是一个空集,或者有一个根节点及两个互不相交的叫做这个根的左树叶

和右树叶的二叉树组成

性质

  • 在二叉树的第i层上至多有2^(i-1)个结点(i>1)。
  • 2.深度为k的二叉树至多有2^k-1个结点(k>=1)。
  • 3.对任何一颗二叉树T,如果其叶子数为n0,度为2的结点数为n2,则n0=n2+1.
  • 4. 具有n个结点的完全二叉树的深度为(log2N)+1。

C语言数据结构_二叉树_07

5.2.2几种特殊的二叉树

满二叉树,完全二叉树,二叉排序树,平衡二叉树。

5.2.3二叉树的存储结构

  • 1.顺序存储
#define MaxSize 100

struct TreeNode{
ElemType value; //结点中的数据元素
bool isEmpty; //结点是否为空
}

main(){
TreeNode t[MaxSize];
for (int i=0; i<MaxSize; i++){
t[i].isEmpty = true;
}
}
  • 2.链式存储
//二叉树的结点

struct ElemType{
int value;
};

typedef struct BiTnode{
ElemType data; //数据域
struct BiTNode *lchild, *rchild; //左、右孩子指针
}BiTNode, *BiTree;

//定义一棵空树
BiTree root = NULL;

//插入根节点
root = (BiTree) malloc (sizeof(BiTNode));
root -> data = {1};
root -> lchild = NULL;
root -> rchild = NULL;

//插入新结点
BiTNode *p = (BiTree) malloc (sizeof(BiTNode));
p -> data = {2};
p -> lchild = NULL;
p -> rchild = NULL;
root -> lchild = p; //作为根节点的左孩子

5.3二叉树的遍历和线索二叉树

5.3.1二叉树的遍历

  • 先序遍历
typedef struct BiTnode{
ElemType data;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;

void PreOrder(BiTree T){
if(T!=NULL){
visit(T); //访问根结点
PreOrder(T->lchild); //递归遍历左子树
PreOrder(T->rchild); //递归遍历右子树
}
}
  • 中序遍历
typedef struct BiTnode{
ElemType data;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;

void InOrder(BiTree T){
if(T!=NULL){
InOrder(T->lchild); //递归遍历左子树
visit(T); //访问根结点
InOrder(T->rchild); //递归遍历右子树
}
}
  • 后序遍历
typedef struct BiTnode{
ElemType data;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;

void PostOrder(BiTree T){
if(T!=NULL){
PostOrder(T->lchild); //递归遍历左子树
PostOrder(T->rchild); //递归遍历右子树
visit(T); //访问根结点
}
}
  • 层次遍历
//二叉树的结点(链式存储)
typedef struct BiTnode{
ElemType data;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;

//链式队列结点
typedef struct LinkNode{
BiTNode * data;
typedef LinkNode *next;
}LinkNode;

typedef struct{
LinkNode *front, *rear;
}LinkQueue;

//层序遍历
void LevelOrder(BiTree T){
LinkQueue Q;
InitQueue (Q); //初始化辅助队列
BiTree p;
EnQueue(Q,T); //将根节点入队
while(!isEmpty(Q)){ //队列不空则循环
DeQueue(Q,p); //队头结点出队
visit(p); //访问出队结点
if(p->lchild != NULL)
EnQueue(Q,p->lchild); //左孩子入队
if(p->rchild != NULL)
EnQueue(Q,p->rchild); //右孩子入队
}
}
  • 遍历序列构造二叉树
先序序列 + 中序序列
后序序列 + 中序序列
层序序列 + 中序序列
key: 找到树的根节点,并根据中序序列划分左右子树,再找到左右子树根节点

5.3.2线索二叉树

1.概念与作用

在二叉树的结点上加上线索的二叉树称为线索二叉树,对二叉树以某种遍历方式(如先序、中序、后序或层次等)进行遍历,使其变为线索二叉树的过程称为对二叉树进行线索化。

2.线索二叉树的存储结构

  1. 中序线索二叉树——线索指向中序前驱、中序后继
  2. 先序线索二叉树——线索指向先序前驱、先序后继
  3. 后序线索二叉树——线索指向后序前驱、后序后继
typedef struct ThreadNode{
int data;
struct ThreadNode *lchild, *rchild;
int ltag, rtag; // 左、右线索标志
}ThreadNode, *ThreadTree;

//全局变量pre, 指向当前访问的结点的前驱
TreadNode *pre=NULL;

void InThread(ThreadTree T){
if(T!=NULL){
InThread(T->lchild); //中序遍历左子树
visit(T); //访问根节点
InThread(T->rchild); //中序遍历右子树
}
}

void visit(ThreadNode *q){
if(q->lchid = NULL){ //左子树为空,建立前驱线索
q->lchild = pre;
q->ltag = 1;
}

if(pre!=NULL && pre->rchild = NULL){
pre->rchild = q; //建立前驱结点的后继线索
pre->rtag = 1;
}
pre = q;
}

//中序线索化二叉树T
void CreateInThread(ThreadTree T){
pre = NULL; //pre初始为NULL
if(T!=NULL);{ //非空二叉树才能进行线索化
InThread(T); //中序线索化二叉树
if(pre->rchild == NULL)
pre->rtag=1; //处理遍历的最后一个结点
}
}
typedef struct ThreadNode{
int data;
struct ThreadNode *lchild, *rchild;
int ltag, rtag; // 左、右线索标志
}ThreadNode, *ThreadTree;

//全局变量pre, 指向当前访问的结点的前驱
TreadNode *pre=NULL;

//先序遍历二叉树,一边遍历一边线索化
void PreThread(ThreadTree T){
if(T!=NULL){
visit(T);
if(T->ltag == 0) //lchild不是前驱线索
PreThread(T->lchild);
PreThread(T->rchild);
}
}

void visit(ThreadNode *q){
if(q->lchid = NULL){ //左子树为空,建立前驱线索
q->lchild = pre;
q->ltag = 1;
}

if(pre!=NULL && pre->rchild = NULL){
pre->rchild = q; //建立前驱结点的后继线索
pre->rtag = 1;
}
pre = q;
}

//先序线索化二叉树T
void CreateInThread(ThreadTree T){
pre = NULL; //pre初始为NULL
if(T!=NULL);{ //非空二叉树才能进行线索化
PreThread(T); //先序线索化二叉树
if(pre->rchild == NULL)
pre->rtag=1; //处理遍历的最后一个结点
}
}
typedef struct ThreadNode{
int data;
struct ThreadNode *lchild, *rchild;
int ltag, rtag; // 左、右线索标志
}ThreadNode, *ThreadTree;

//全局变量pre, 指向当前访问的结点的前驱
TreadNode *pre=NULL;

//先序遍历二叉树,一边遍历一边线索化
void PostThread(ThreadTree T){
if(T!=NULL){
PostThread(T->lchild);
PostThread(T->rchild);
visit(T); //访问根节点
}
}

void visit(ThreadNode *q){
if(q->lchid = NULL){ //左子树为空,建立前驱线索
q->lchild = pre;
q->ltag = 1;
}

if(pre!=NULL && pre->rchild = NULL){
pre->rchild = q; //建立前驱结点的后继线索
pre->rtag = 1;
}
pre = q;
}

//先序线索化二叉树T
void CreateInThread(ThreadTree T){
pre = NULL; //pre初始为NULL
if(T!=NULL);{ //非空二叉树才能进行线索化
PostThread(T); //后序线索化二叉树
if(pre->rchild == NULL)
pre->rtag=1; //处理遍历的最后一个结点
}
}

5.4树与二叉树的应用

1.二叉排序的定义

左叶子<跟结点值<右叶子树结点

2.查找

3.插入

4.二叉树的构造

5.删除操作

6.查找效率分析

第六章 排序

6.1排序的基本概念

  • 排序:重新排列表中的元素,使表中元素满足按关键字有序的过程(关键字可以相同)
  • 排序算法的评价指标:时间复杂度、空间复杂度;
  • 排序算法的稳定性:关键字相同的元素在排序之后相对位置不变,称为稳定的;(选择题考查)
  • Q: 稳定的排序算法一定比不稳定的好?
  • A: 不一定,要看实际需求;
  • 排序算法的分类:
  • 内部排序: 数据都在内存——关注如何使时间、空间复杂度更低;
  • 外部排序: 数据太多,无法全部放入内存——关注如何使时间、空间复杂度更低,如何使读/写磁盘次数更少;

6.2插入排序

  • 直接插入
  • 折半插入
  • 希尔排序
void InsertSort(int A[], int n){    //A中从1开始存储,0放哨兵
int i,j;
for(i=1; i<n; i++)
if(A[i]<A[i-1]){
A[0] = A[i]; //复制为哨兵
for(j=i-1; A[0] < A[j]; --j) //从后往前查找待插入位置
A[j+1] = A[j]; //向后挪动
A[j+1] = A[0]; //复制到插入位置
}
}
void InsertSort(int A[], int n){ 
int i,j,low,high,mid;
for(i=2;i<=n;i++){
A[0] = A[i]; //将A[i]暂存到A[0]
low = 1; high = i-1; //折半查找的范围

while(low<=high){ //折半查找
mid = (low + high)/2; //取中间点
if(A[mid]>A[0]) //查找左半子表
high = mid - 1;
else //查找右半子表
low = mid + 1;
}

for(j=i-1; j>high+1;--j) //统一后移元素,空出插入位置
A[j+1] = A[j];
A[high+1] = A[0]
}
}
void ShellSort(ElemType A[], int n){
//A[0]为暂存单元
for(dk=n/2; dk>=1; dk=dk/2) //步长递减(看题目要求,一般是1/2
for(i=dk+1; i<=n; ++i)
if(A[i]<A[i-dk]){
A[0]=A[i];
for(j=i-dk; j>0&&A[0]<A[j];j-=dk)
A[j+dk]=A[j]; //记录后移,查找插入的位置
A[j+dk]=A[0;] //插入
}
}

6.3交换排序

  • 1.冒泡排序
  1. 第一趟排序使关键字值最小的一个元素“冒”到最前面(其最终位置)—— 每趟冒泡的结果是把序列中最小元素放到序列的最终位置,这样最多做n-1趟冒泡就能把所有元素排好序;
  2. 为保证稳定性,关键字相同的元素不交换;
//交换
void swap(int &a, int &b){
int temp = a;
a = b;
b = temp;
}

//冒泡排序
void BubbleSort(int A[], int n){ //从0开始存放
for(int i=0; i<n-1; i++){
bool flag = false; // 表示本趟冒泡是否发生交换的标志
for(int j=n-1; j>i; j--) //一趟冒泡过程
if(A[j-1]>A[j]){ //若为逆序
swap(A[j-1],A[j]); //交换
flag=true;
}
if(flag==false)
return; //本趟遍历后没有发生交换,说明表已经有序,可以结束算法
}
}
  • 2.快速排序
  1. 每一趟排序都可使一个中间元素确定其最终位置
  2. 用一个元素(不一定是第一个)把待排序序列“划分”为两个部分,左边更小,右边更大,该元素的最终位置已确认
//用第一个元素将待排序序列划分为左右两个部分
int Partition(int A[], int low, int high){
int pivot = A[low]; //用第一个元素作为枢轴
while(low<high){
while(low<high && A[high]>=pivot) --high; //high所指元素大于枢轴,high左移
A[low] = A[high]; //high所指元素小于枢轴,移动到左侧

while(low<high && A[low]<=pivot) ++low; //low所指元素小于枢轴,low右移
A[high] = A[low]; //low所指元素大于枢轴,移动到右侧
}
A[low] = pivot //枢轴元素存放到最终位置
return low; //返回存放枢轴的最终位置
}

//快速排序
void QuickSort(int A[], int low, int high){
if(low<high) //递归跳出条件
int pivotpos = Partition(A, low, high); //划分
QuickSort(A, low, pivotpos - 1); //划分左子表
QuickSort(A, pivotpos + 1, high); //划分右子表
}


6.4选择排序

//n个元素简单选择排序需要n-1趟进行处理
//交换
void swap(int &a, int &b){
int temp = a;
a = b;
b = temp;
}

void SelectSort(int A[], int n){ //A从0开始
for(int i=0; i<n-1; i++){ //一共进行n-1趟,i指向待排序序列中第一个元素
int min = i; //记录最小元素位置
for(int j=i+1; j<n; j++) //在A[i...n-1]中选择最小的元素
if(A[j]<A[min]) min = j; //更新最小元素位置
if(min!=i)
swao(A[i],A[min]); //交换
}
}

6.5归并排序

归并(Merge):把两个或多个已经有序的序列合并成一个;
//创建辅助数组B
int *B=(int *)malloc(n*sizeof(int));

//A[low,...,mid],A[mid+1,...,high] 各自有序,将这两个部分归并
void Merge(int A[], int low, int mid, int high){
int i,j,k;
for(k=low; k<=high; k++)
B[k] = A[k]; //将A中所有元素复制到B中
for(i=low, j=mid+1, k=i; i<=mid && j<= high; k++){
if(B[i]<=B[j]) //为保证稳定性两个元素相等时,优先使用靠前的那个
A[k]=B[i++]; //将较小值复制到A中
else
A[k]=B[j++];
}//for

//没有归并完的部分复制到尾部,while只会执行一个
while(i<=mid) A[k++]=B[i++]; //若第一个表未检测完,复制
while(j<=high) A[k++]=B[j++]; //若第二个表未检测完,复制
}

//递归操作
void MergeSort(int A[], int low, int high){
if(low<high){
int mid = (low+high)/2; //从中间划分
MergeSort(A, low, mid); //对左半部分归并排序
MergeSort(A, mid+1, high); //对右半部分归并排序
Merge(A,low,mid,high); //归并
}if
}

6.6基数排序

空间效率:O®, 其中r为基数,需要的辅助空间(队列)为r;
稳定性:稳定!
基数排序擅长解决的问题
数据元素的关键字可以方便地拆分为d组,且d较小;
每组关键字的取值范围不大,即r较小;
数据元素个数n较大;

C语言数据结构_结点_08