数据结构
线性表
声明结构体用typedef
静态分配方式用静态数组
length存放顺序表的当前长度
线性表要存放 数据元素 和 顺序表当前长度
可以理解为一个数据域:数据元素 和 一个指针域:顺序表当前长度
typedef struct { int data[MaxSize]; int length; }SqList;
结构体变量作函数参数时,用引用
初始化线性表时,第一,要把每个元素都赋值为0;第二,要把线性表的长度赋值为0
void Init(SqList &L) { for(int i = 0;i < MaxSize;i++) { L.data[i] = 0; } L.length = 0; }
在main函数中的初始化
int main(void) { SqList L; //初始化一个结构体变量,用类型 名称来写,int a一样 InitList(L); }
插入元素,头插法
插入的位置后,从后往前,前一个元素往后挪一个位置,为待插入的元素留出空间
注意下标的起始,线性表从1开始,而数组下标从0开始。操作数 i 是从1开始,存到数组中应该是从 i-1 开始
插入元素一共要提供三个参数:插入的线性表,插入的位置,插入的元素值
void ListInsert(SqList &L,int i,int e) { for(int j = L.length;j >= i;j--) { // 插入第i-1号位置,所以要从i到Length末尾都要后移一位 L.data[j] = L.data[j-1]; } L.data[i-1] = e; L.length++; }
插入元素应该加上对插入位置的判断
合法的插入范围是1->length+1,即数组中的0->length; length+1是添加到末尾的后一个位置
bool ListInsert(SqList &L,int i,int e) { if(i < 1 || i > length+1) return false; if(L.length >= MaxSize) return false; for(int j = L.length;j >= i;j--) { L.data[j] = L.data[j-1]; } L.data[i-1] = e; L.length++; return true; }
按值查找,一个一个地比较
一定要明确循环的是什么,这个地方循环的是数组下标,而不是线性表的实际位置,从0->length
int LocateElem(SqList &L,int e) { for(int i = 0;i < length;i++) { if(L.data[i] == e) return i+1; // 返回的是线性表中的实际位置,数组下标+1 } return -1; }
线性表是随机存取,不需要一个一个地比较,直接根据数组下标去寻找即可
int GetElem(SeqList &L,int i) { //传入的是线性表的位置 return data[i-1]; //返回的是数组中的位置对应的数据,要-1 }
线性表元素删除
bool ListDelete(SeqList &L,int i,int &e) { if(i < 1||i > L.length) { //给出的删除位置为线性表的位置1-length,而不是数组下标 return false; // 首先判断要删除的元素是否合法 } e=L.data[i-1]; //将要被删除的元素赋值给e,后面返回 for(int j = i;j < L.length;j++) { L.data[j-1] = L.data[j]; //将删除元素后面的元素,从后往前移动 } L.length--; //别忘记修改指针域,即length return true; }
动态拓展内存空间
初始化动态空间的线性表时,用malloc
malloc返回的是一个指针,指向了多大的内存空间的地址
malloc返回的内存空间指针指向线性表的数据域
#define InitSize 10 typedef struct { int *data; //数据域用指针 int MaxSize; int length; }SeqList; void InitList(SeqList &L) { L.data=(int*)malloc(InitSize*sizeof(int)); // malloc的参数是,多少个元素*每个元素的大小 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)); //给L重新申请一段更大的内存空间,相当于清零 for(int i = 0;i < L.length;i++) { L.data[i] = p[i]; //将原有的p指针指向的内存空间元素依次赋值给新地址L } L.MaxSize = L.MaxSize + len; free(p); }
单链表
单链表是next指针,二叉树是左右孩子lchild和rchild
typedef struct LNode {
ElemType data;
struct LNode *next;
}LNode,*LinkList;
//相当于typedef struct LNode{} LNode;与typedef struct LNode{} *LinkList;
typedef struct LNode LNode;
typedef struct LNode *LinkList;
typedef struct LNode {
ElemType data;
struct LNode *next;
}LNode,*LinkList;
LNode * L 与 LinkList L效果相同,都是声明一个指向单链表第一个节点的指针
强调这是一个单链表,用LinkList
强调这是一个节点,用LNode *
typedef struct LNode { int data; struct LNode * next; }LNode,*LinkList; typedef struct LNode { int data; struct LNode *next; //不要忘记在定义next指针时,用struct关键字,struct LNode *next,定义同类型内部指针时,要带struct,struct LNode* next; }LNode,*LinkList;
typedef struct LNode { int data; struct LNode *next; }LNode,*LinkList; //初始化一个单链表,带头节点 bool InitList(LinkList &L) { L = (LNode *)malloc(sizeof(LNode)); // 分配一个头节点 if(L == NULL) { //如果申请后,L仍为NULL,则内存不足,申请失败 return false; } L -> next = NULL; //头节点之后暂时还没有节点 return true; } bool Empty(LinkList L) { if(L -> next == NULL) return true; //头指针指向头节点,如果头指针的下一个为空,则单链表为空 return false; } void test() { LinkList L; InitList(L); }
malloc返回的是一个指针,需要强制类型转换为对应指针类型,传入的参数是多少个元素*每个元素的大小
带头节点的单链表 1. 申请一个节点大小的内存空间 2.判断L是否为NULL,内存够不够 3.将头节点的下一个节点地址域指向空 4.如果申请成功,返回true
在第i个位置插入元素e
bool ListInsert(LinkList &L,int i,int e) { if(i < 1) return false; LNode *p; //指针p指向当前扫描到的节点 int j = 0; //循环变量,用来判断当前是第几个节点 p = L; //初始状态下,L指向头节点,头节点是第0个节点 while(p!=NULL && j < i-1) { //p不是空,说明没循环到末尾,j<i-1说明还没有循环到第i-1个位置 p = p->next; j++; } if(p == NULL) { //循环到末尾也没有找到,返回false return false; } LNode *s = (LNode *)malloc(sizeof(LNode)); //重新开辟一个节点的内存大小,申请的是节点的指针 s -> data = e; s -> next = p -> next; p -> next = s; return true; } // 1.判断插入位置是否合法 // 2.声明循环指针p指向当前扫描到的节点,循环变量j判断当前是第几个节点,这里p强调是一个节点,所以用LNode * // 3.初始状态下,循环指针p指向头指针 // 4.循环指针后移,直到移动到循环变量j和i-1相等且没到末尾 // 5.循环到末尾也没找到,p==NULL,返回false // 6.申请一个节点大小的内存单元s,申请的是节点的指针 // 7.改变s和p指针的指向关系 // 8.成功返回true //单链表的插入操作,需要一个循环变量计数和一个循环指针,去找到应该循环d
p指针后移: p = p-> next; 下一个指针的地址域赋给上一个,令上一个节点后移一个单位
删除节点:首先要找到第i-1个节点,将其指针指向第i+1个节点,并释放第i个节点
bool ListDelete(LinkList &L,int i,ElemType e) { if(i < 1) return false; LNode *p; int j = 0; p = L; while(p != NULL && j < i-1) { //删除第i-1后面的第i号节点的位置 p = p -> next; j++; } if(p == NULL) { return false; } if(p -> next == NULL) { //第i-1号节点后面再无节点 return false; } LNode *q = p -> next; //令指针q指向被删除节点,p循环到删除节点的上一个节点 e = q -> data; p->next = q -> next; free(q); return true; } // 1. 找位置 // 2. 改节点,将被删除节点的上一个节点的next指针指向被删除节点的next // 3. 释放内存,将被删除节点的内存释放掉
单链表求长度
int length(LinkList &L) { int len = 0; LNode *p = L; //将循环指针p指向头节点L while(p -> next != NULL) { //带头节点,头节点不算入长度的话 p = p -> next; len ++; } return len; } int length(LinkList &L) { int len = 0; LNode *p = L -> next; //跳过头节点 while(p != NULL) { p = p -> next; } return len; }
尾插法建立单链表
LinkList List_TailInsert(LinkList &L) {
int x;
L = (LinkList)malloc(sizeof(LNode));
LNode *s,*r = L;
scanf("%d",&x);
while(x != 9999) {
s = (LNode*)malloc(sizeof(LNode));
s -> data = x;
r -> next = s;
r = s;
scanf("%d",&x);
}
r -> next = NULL;
return L;
}
// 后插操作
// 1. 将指针r指向要插入节点的上一个位置
// 2. 申请插入节点s并赋值
// 3. r的next指针指向s
// 4. r后移一步指向s,为下一步的操作做准备
// 最后将最后一个节点的nextz
二叉树
顺序存储
- 几个常考的基本操作
- i的左孩子
- i的右孩子
- i的父节点
- i所在的层次
- 二叉树的顺序存储中,一定要把二叉树的节点编号和完全二叉树一一对应起来
链式存储
二叉链表
- 找到节点p的左右孩子节点时间复杂度低
- 但是找某个节点的父节点,只能从根节点开始遍历
struct ElemType {
int value;
}
typedef struct BiTNode {
ElemType data; //数据域
struct BiTNode *lchild,*rchild; //指针域
}BiTNode,*BiTree; //节点和树根节点的指针
//BiTNode* 和 BiTree等价,但是侧重方面不同
//定义一棵空树
//声明一个指向根节点的指针,初始为NULL
BiTree root = NULL;
//插入根节点
root = (BiTree)malloc(sizeof(BiTNode));
root -> data = {1};
root -> lchild = NULL;
root -> rchild = NULL;
//插入新节点
BiTNode *p = (BiTNode*)malloc(sizeof(BiTNode));
p -> data = {2};
p -> lchild = NULL;
p -> rchild = NULL;
root -> lchild = p; //作为根节点的左孩子节点
//插入新节点:分配一个节点大小的内存空间,给数据域赋值,并修改左右孩子和父指针
三叉链表
- 包含左右孩子节点和父节点指针
- 三叉链表的目的是为了方便寻找父节点
typedef struct BiTNode {
ElemType data;
struct BiTNode *lchild,*rchild;
struct BiTNode *parent;
}BiTNode,*BiTree;
求树的深度
int treeDepth(BiTree T) { //接收二叉树的节点作为参数,通常是根节点
if(T == NULL) { //如果传入的节点是NULL,则返回0,因为空树的高度为0
return 0;
}
//if T == NULL 一个是判断这棵树是否为空树,另一个是当递归到叶子节点时,可以返回0+1=1
else {
int l = treeDepth(T->lchild);
int r = treeDepth(T->rchild);
//是根据左右子树高度的最大值,应该包含根节点,所以应该+1
if(l > r) return l+1;
else return r+1;
}
}
- 递归左右子树高度:treeDepth(T -> lchild); treeDepth(T -> rchild);
判断节点总数
int count = 0;
void Count(BiTree T) {
if(T == NULL) {
return 0;
}
else {
count++;
Count(T->lchild);
Count(T->rchild);
}
}
- 判断节点总数,递归左右子树,并在递归左右子树之前要count++
- 二叉树判断左右子树高度和统计节点总数,都要递归实现,并递归返回的条件是传入的节点为空
设计算法按前序次序打印二叉树中的叶子结点
void PrintLeaves(BiTree T) {
if(T == NULL) {
return;
}
if(T -> lchild == NULL && T -> rchild == NULL) { //判断是否为叶子节点
printf("%d",T->data); // 只打印叶子节点
}
PrintLeaves(T -> lchild); //递归处理左子树
PrintLeaves(T -> rchild); //递归处理右子树
}
前序序列打印所有节点
和只打印叶子节点相比,少了一个对是否为叶子节点的判断,即
if(T -> lchild == NULL && T -> rchild == NULL) {} if(T -> lchild == NULL && T -> rchild == NULL){} //叶子节点有一个判断,即左右孩子是否为空 if(T -> lchild == NULL && T -> rchild == NULL){} if(T -> lchild -- NULL && T -> rchild == NULL){}
void PrintPreorder(BiTree T) { if(T == NULL) { return; } else { printf("%d",T -> data); PrintPreorder(T -> lchild); PrintPreorder(T -> rchild); } }
已知数组A[n]中的元素为整型,设计算法将其调整为左右两部分,左边所有元素为偶数,右边所有元素为奇数,并要求算法的时间复杂度为O(n)
void Array_reverse() {
int i = 0,j = n-1;
while(i < j) { //i是左侧,j是右侧,只有当i指针在j指针的左侧时,才继续进行交换操作
while(a[i] % 2 == 0) i++; //a[i]满足要求,i指针后移,直到遇到一个奇数
while(a[j] % 2 != 0) j--; //a[j]满足要求,j指针前移,直到遇到一个偶数停止
if(i < j) swap(a[i],a[j]);
}
}
- 从数组的两端向中间比较,设置两个变量i和j,初始时i=0,j=n-1,若A[i]为偶数并且A[j]为奇数,则将A[i]与A[j]交换。
试写出带头节点的单链表逆置算法,请写出结点结构???
LinkList Reverse(LinkList L) {
LNode *p,*r; //前指针和后指针
p = L -> next;
L -> next = NULL;
while(p != NULL) {
r = p -> next;
p -> next = L -> next;
L -> next = p;
p = r;
}
return L;
}
//第一步,设置前后指针
//第二步,p为第一个元素位置,断开头节点的下一个位置,断链
//第三步,判断p是否为空
//第四步,r指针后移
//第五步,p->next = L->next;
//第六步,L->next = p;
//第七步,p指针后移指向r
//zui'ho
关键路径每次都取最大值
插入排序
void InsertSort(int a[],int len) {
for(int j = 1;j < len;j++) { //外部循环,从数组的第二个位置遍历到最后一个位置,外部循环控制我们要将哪个元素插入到已经排序的子数组中
int key = a[j];
int i = j - 1; //i从当前元素的前一个元素开始
while(i >= 0 && a[i] > key) {
a[i+1] = a[i];
i--;
}
a[i+1] = key;
}
}
堆排序
- 堆必须是一颗完全二叉树
- 在小根堆中,每个父节点都必须小于子节点元素
- 在大根堆中,每个父节点都必须大于子节点元素
- 按照层序遍历的顺序来给节点编号
上滤
- 当叶子节点破坏了堆序性,让他和他的父元素比较,若大于父节点则交换,直到无法上移为止,
下滤
- 将破坏堆序性的元素跟他的最大的子节点比较,如果小于他的最大子节点,则交换
- 持续比较,直到该元素大于他的子节点位置,或者移动到底部为止
总之,上滤是和父节点比较,下滤是和子节点比较,只能父子之间交换
建堆
- 自顶向下建堆法
- 将元素一个一个插入到堆内,将新元素放到堆的最后一位,然后对其进行上滤操作
取最值调整
- 在大根堆中,如果父节点比两个子节点都要小,则选最大的往上走
- 在小根堆中,如果父节点比两个子节点都要大,则选最小的往上走