一.线性表
1. 顺序表
(1)顺序表特点
(2)顺序表创建
#define maxsize 100
typedef int ElemType; //定义基本的数据类型
typedef struct //定义线性表的物理储存方式
{
ElemType data[maxsize];
int length;
}SqList;
//由数组元素a[0...n-1]创建顺序表L
void CreatList(SqList* L, ElemType a[], int n)
{
int i = 0, k = 0;
L = (SqList*)malloc(sizeof(SqList));
while (i < n)
{
L->data[k] = a[i]; //将元素a[i]存到L中去
k++;
i++;
}
L->length = k; //设置表L的长度为k
}
(3)顺序表基本操作
//初始化线性表
void InitList(SqList* L)
{
L = (SqList*)malloc(sizeof(SqList));
L->length = 0;
}
//销毁线性表
void DestroyList(SqList* L)
{
free(L);
L->length = 0;
}
//判断是否为空表
bool ListEmpty(SqList* L)
{
return (L->length == 0);
}
//求线性表的长度
int ListLength(SqList* L)
{
return (L->length);
}
//输出线性表
void DisList(SqList* L)
{
for (int i = 0; i < L->length; i++)
{
printf("%d ", L->data[i]);
}
printf("\n");
}
//取出线性表中的元素
bool GetElem(SqList* L, int i, ElemType e)
{
if (i<1 || i>L->length)
return false;
else
{
e = L->data[i - 1]; //第i-1的单元存储着第i个数据
}
return true;
}
//按(元素)值查找
int LocateElem(SqList* L, ElemType e)
{
int i = 0;
for (i = 0; i < L->length; i++)
{
if (e == L->data[i])
return i + 1; //返回逻辑序号
}
return 0; //查找失败
}
//插入数据元素
bool ListInsert(SqList* L, int i, ElemType e)
{
if (i<1 || i>L->length || (L->length == maxsize))
{
return false;
}
i = i - 1; //先将逻辑符号转成数组下标(物理符号)
for (int j = L->length; j < i; j--) //L->length记录的数组下标
{
L->data[j] = L->data[j - 1]; //都往后移动一位
}
L->data[i] = e; //放入新数据
L->length++; //长度加一
return true;
}
//删除数据元素
bool ListDelete(SqList* L, int i)
{
if (i<1 || i>L->length)
return false;
i = i - 1; //将逻辑符号转成数组下标(物理符号)
for (int j = i; j < L->length; j++)
{
L->data[j] = L->data[j + 1];
}
L->length--;
return true;
}
2.链式存储结构——链表
(1)基础知识
(2)单链表
(传值和传址)
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode *next;
}SLTNode;
//打印链表
void SListPrint(SLTNode* phead)
{
SLTNode* p = phead;
while(p != NULL)
{
printf("%d ", p->data);
p = p->next;
}
prinft("NULL\n");
}
//创建结点
SLTNode* Creatnode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1); //完全结束程序
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//头插
void SList_HeadInsert(SLTNode** phead, SLTDataType x)
{
SLTNode* newnode = Creatnode(x);
newnode->next = *phead;
*phead = newnode; //使用二级指针,就可以改变phead了
}
//尾插
void SList_EndInsert(SLTNode** phead,SLTDataType x)
//要改变链表phead需要用到二级指针
//传指针,然后用指针接受,当然是传值调用
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
//因为内存上不相邻,所以先开辟和后开辟内存都是一样的
newnode->data = x;
newnode->next = NULL;
if (*phead == NULL) //对二级指针解引用
{
*phead = newnode; //把一级指针改成newnode,而不是二级指针
}
else
{
SLTNode *p = *phead; //要存一级指针(*phead),phead是个二级指针,需要先解引用
while (p->next != NULL)
{
p = p->next;
}
p->next = newnode;
}
}
//头删
void SList_HeadDelete(SLTNode** phead)
{
if (*phead == NULL)
{
return;
}
else
{
SLTNode* q = (*phead)->next;
free(*phead);
*phead = q;
}
}
//尾删
void SList_EndDelete(SLTNode** phead)
//因为删到最后一个的时候要改变原指针为空,所以还是传二级
{
if (*phead == NULL)
{
return;
}
if ((*phead)->next == NULL)
{
free(*phead);
*phead = NULL;
}
else
{
SLTNode* p = *phead;
while (p->next->next != NULL)
{
p = p->next;
}
free(p->next);
p->next = NULL;
}
}
//头插
void SList_HeadInsert(SLTNode** phead, SLTDataType x)
{
SLTNode* newnode = Creatnode(x);
newnode->next = *phead;
*phead = newnode; //使用二级指针,就可以改变phead了
}
//查找
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
SLTNode* p = phead;
while (p)
{
if (p->data == x)
{
return p;
}
else
{
p = p->next;
}
}
return NULL;
}
//在pos之前一个位置插入
void SListInsert_1(SLTNode** phead, SLTNode* pos, SLTDataType x)
{
if (pos==*phead)
{
SList_HeadInsert(phead, x); //当在最前面插入时,为头插
}
else
{
SLTNode* newnode = Creatnode(x);
SLTNode* p = *phead;
while (p->next != pos) //找到pos前一个结点
{
p = p->next;
}
p->next = newnode;
newnode->next = pos;
}
}
//在pos之后一个位置插入(更适合单链表)
void SListInsert(SLTNode* pos, SLTDataType x)
{
SLTNode* newnode = Creatnode(x);
newnode->next = pos->next;
pos->next = newnode;
}
//删除pos位置的结点
void SListDelete(SLTNode** phead,SLTNode* pos)
{
if (pos==*phead) //用头删
{
SList_HeadDelete(phead);
}
else
{
SLTNode* p = *phead;
while (p->next != pos)
{
p = p->next;
}
p->next = pos->next;
free(pos);
}
}
//销毁整个链表
void DistoryList(SLTNode** phead)
{
assert(phead); //assert是当括号内为假(0)的时候报错
SLTNode* p = *phead;
while (p)
{
SLTNode* q = p->next;
free(p);
p = q;
}
*phead = NULL; //销毁链表后,phead也用不到了,所以置空
}
//手动构建链表,方便调试oj代码
int main()
{
struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode* n2 = (struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode* n3 = (struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode* n4 = (struct ListNode*)malloc(sizeof(struct ListNode));
n1->val = 7;
n2->val = 7;
n3->val = 7;
n4->val = 7;
n1->next = n2;
n2->next = n3;
n3->next = n4;
n4->next = NULL;
return 0;
}
(3)单循环链表
循环链表大多用尾指针,因为更方便,时间复杂度更低
//带尾指针循环链表的合并
LinkNode* Connect(LinkNode* Ta, LinkNode* Tb)
{
LinkNode* p = Ta->next; //因为之后Ta->next 要被改变,所以先保存起来
Ta->next = Tb->next->next; //因为头结点合并时不保留,所以是Tb->next->next
free(Tb->next); //释放Tb的表头(头结点)
Tb->next = p; //Tb->next指向p,把Tb表尾指向Ta表头
return Tb;
}
(4)带头双向循环链表
typedef int LTdataType;
typedef struct ListNode
{
LTdataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
//双链表初始化
LTNode* ListInit()
{
LTNode* phead =(LTNode*)malloc(sizeof(LTNode));
phead->next = phead;
phead->prev = phead;
return phead;
}
//创造新结点
LTNode* Creatnode(LTdataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
//打印链表
void ListPrint(LTNode* phead)
{
LTNode* cur = phead->next;
while (cur != phead)
{
prinft("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
//尾插
void List_EndInsert(LTNode* phead, LTdataType x)
{
LTNode* tail = phead->prev;
//先找尾
LTNode* newnode = Creatnode(x);
//链接
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
//尾删
void List_EndDelete(LTNode* phead)
{
if ((phead->next == phead) || phead == NULL) //头结点不能删
{
printf("删空了!\n");
return;
}
LTNode* tail = phead->prev;
LTNode* newtail = phead->prev;
free(tail);
newtail->next = phead;
phead->prev = newtail;
}
//头插
void List_HeadInsert(LTNode* phead, LTdataType x)
{
if (!phead)
{
printf("phead为空指针!\n");
return;
}
LTNode* newnode = Creatnode(x);
LTNode* cur = phead->next;
newnode->next = cur;
cur->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
}
//头删
void List_HeadDelete(LTNode* phead) //因为有头结点,所以不会改变phead,所以一级指针就够了
{
if ((phead->next == phead) || phead == NULL) //头结点不能删
{
printf("删空了!\n");
return;
}
LTNode* target = phead->next; //尽量多定义变量
LTNode* next = target->next;
phead->next = next;
next->prev = phead;
free(target);
}
//查找
LTNode* FindNode(LTNode* phead, LTdataType x)
{
if (!phead)
{
printf("phead为空指针!\n");
return;
}
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
//在pos之前插入
void PosInsert(LTNode* pos, LTdataType x)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* newnode = Creatnode(x);
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
//对应的头插
void pos_head(LTNode* phead, LTdataType x)
{
assert(phead);
PosInsert(phead->next, x);
}
//对应的尾插
void pos_End(LTNode* phead, LTdataType x)
{
assert(phead);
PosInsert(phead, x);
}
//删除pos位置
void pos_Erase(LTNode* pos)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
}
//对应的头删
void pos_delete(LTNode* phead, LTdataType x)
{
assert(phead);
pos_Erase(phead->next);
}
//对应的尾删
void pos_delete(LTNode* phead, LTdataType x)
{
assert(phead);
PosInsert(phead->prev);
}
//销毁链表
void ListDestroy(LTNode* phead)
//因为是一级指针,所以最后需要在函数引用完之后把phead置空
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
(6)链式存储结构优缺点
三.栈和队列
1.栈
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void StackInit(ST* st)
{
assert(st);
st->a = NULL;
st->top = 0;
st->capacity = 0;
}
bool StackEmpty(ST* st)
{
assert(st);
return st->top == 0;
}
void StackDestroy(ST* st)
{
assert(st);
free(st->a);
st->a = NULL;
st->top = st->capacity = 0;
}
//压入栈中
void StackPush(ST* st,STDataType x)
{
assert(st);
if (st->top == st->capacity) //扩容
{
int newCapacity = (st->capacity == 0 ? 4 : st->capacity * 2);
STDataType* tmp = (STDataType*)realloc(st->a, sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
st->a = tmp;
st->capacity = newCapacity;
}
st->a[st->top] = x;
st->top++;
}
//从栈中拿出
void StackPop(ST* st)
{
assert(st);
if (StackEmpty(st))
{
printf("栈是空的\n");
return ;
}
st->top--; //之后的空间还有可能用到,当真正用不到的时候再用StackDestroy来释放
}
STDataType StackTop(ST* st)
{
assert(st);
if (StackEmpty(st))
{
printf("栈是空的\n");
return -1;
}
return st->a[st->top - 1]; //栈顶与数组实际上还差一
}
STDataType StackSize(ST* st)
{
assert(st);
return st->top;
}
2.队列
typedef int QDataType;
typedef struct QueueNode //队列的结点(像链表)
{
struct QueueNode* next;
QDataType data;
}QueueNode;
typedef struct Queue //队列只需要控制两个出入口就行了
//如果不用结构体就要把两个指针拆开写,而且还要用二级指针
{
QueueNode* head;
QueueNode* tail;
int size;
}Queue;
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->head;
while (cur != NULL)
{
QueueNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
//判断队列空不空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return (pq->head == NULL && pq->tail == NULL);
}
void QueuePrint(Queue* pq)
{
while (!QueueEmpty(pq))
{
QDataType front = QueueFront(pq);
printf("%d ", front);
QueuePop(pq);
}
printf("\n");
}
//进数据 (因为队列进入的方向被固定了,只有一个方向进)
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
newnode->data = x;
newnode->next = NULL;
if (pq->head == NULL)
{
pq->head = pq->tail = newnode;
}
else //链接到队尾
{
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
//出数据(一个一个出)
void QueuePop(Queue* pq)
{
assert(pq);
if (QueueEmpty(pq))
{
printf("队列的头指针为空\n");
return;
}
//一个结点
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL; //当head指向最后的空指针时,队列已经被删完了,tail要置空,不然tail野指针
}
else //头删
{
QueueNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
pq->size--;
}
//取队头的数据
QDataType QueueFront(Queue* pq)
{
assert(pq);
if (QueueEmpty(pq))
{
printf("队列的头指针为空\n");
return -1;
}
return pq->head->data;
}
//取队尾的数据
QDataType QueueBack(Queue* pq)
{
assert(pq);
if (QueueEmpty(pq))
{
printf("队列的头指针为空\n");
return -1;
}
return pq->tail->data;
}
//队列个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
四.树
1.基本概念
左孩子右兄弟表示法:
2. 基础二叉树
(1)概念
(图示位置就是从左到右不连续的)
(2)数组存储
(3)堆
(4)堆的实现(以小堆为例)
改成大堆需要修改Adjustup,AdjustDown,HeapPush等的大小于号。
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
void HeapInit(HP* php);
void Swap(HPDataType* p1, HPDataType* p2);
void HeapDestroy(HP* php);
void HeapPush(HP* php, HPDataType x);
void AdjustUp(HPDataType *a, int child); //从孩子的位置开始向上调
void AdjustDown(int* a, int n, int parent); //从父亲的位置开始向下调
//这两个函数的前提都是左子树和右子树是大堆/小堆
void HeapPop(HP* php);
HPDataType HeapTop(HP* php);
bool HeapEmpty(HP* php);
int HeapSize(HP* php);
void PrintHeap(HP* php);
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void HeapInit(HP* php)
{
assert(php);
php->a = NULL;
php->capacity = 0;
php->size = 0;
}
void HeapDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->capacity = php->size = 0;
}
void AdjustUp(HPDataType* a, int child) //调试: php->a,6 看6个
{
int parent = (child - 1) / 2;
while (child>0) //到根的时候child等于0了
{
if (a[child] < a[parent]) //这是小堆排列,大堆相反 a[child] > a[parent]
{
Swap(&a[child], &a[parent]); //别忘了取地址
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void HeapPush(HP* php, HPDataType x)
{
assert(php);
if (php->size == php->capacity) //扩容
{
int newcapacity = (php->capacity == 0 ? 4 : php->capacity * 2);
HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
return;
}
php->capacity=newcapacity;
php->a = tmp;
}
//1.插入
php->a[php->size] = x;
php->size++;
//2.向上调整
AdjustUp(php->a, php->size-1); //传过去的是孩子的位置,而孩子的位置(size)刚++了,所以减掉
}
void AdjustDown(int *a,int size,int parent)
{
int child = parent * 2 + 1; //假设左孩子是小的
while (child < size) //孩子出界了结束
{
//选出左右孩子中小/大的那一个,child+1<size 保证了只有左孩子没有右孩子的情况,就是说不用与右孩子作比较了,最小的就是左孩子!
if (child+1<size && a[child + 1] < a[child]) //要是右孩子比左孩子小,那么孩子就是右孩子(= parent * 2 + 2) 大堆:a[child + 1] > a[child]
{
child++;
}
if (a[child] < a[parent]) //大堆: a[child] > a[parent]
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1; //继续假设左孩子是小的
}
else
{
break;
}
}
}
//删除堆顶数据
void HeapPop(HP* php)
{
assert(php);
if (HeapEmpty(php))
{
printf("堆是空的\n");
return;
}
//1.先首尾交换,再删除,不然直接删除会破坏堆的结构
Swap(&php->a[0], &php->a[php->size - 1]); //别忘了取地址,取地址
php->size--; //删除操作
//2.删除之后就不是堆了,向下调整为堆
AdjustDown(php->a, php->size, 0);
}
HPDataType HeapTop(HP* php)
{
assert(php);
if (HeapEmpty(php))
{
printf("堆是空的\n");
return;
}
return php->a[0]; //注意取堆顶元素是a[0],不是a[php->size - 1]
}
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
int HeapSize(HP* php)
{
assert(php);
return php->size;
}
void PrintHeap(HP* php)
{
assert(php);
while (!HeapEmpty(php))
{
int top = HeapTop(php);
printf("%d ", top);
HeapPop(php);
}
}
(5)堆的应用
1. TopK问题
第K小就用大根堆,第K大就用小根堆
void CreateNData() //造数据
{
int n = 10000;
srand(time(0));
const char* file = "data.txt"; //创建文件
FILE* fin = fopen(file, "w");
if (fin == NULL)
{
perror("fopen error");
return;
}
for (size_t i = 0; i < n; i++)
{
int x = rand() % 10000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
void printTopk(int k)
{
const char* file = "data.txt";
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
perror("fopen error");
return;
}
int* kmin_heap = (int*)malloc(sizeof(int) * k);
if (kmin_heap == NULL)
{
perror("malloc fail");
return;
}
for (int i = 0; i < k; i++)
{
fscanf(fout, "%d", &kmin_heap[i]); //从文件中读取到堆里
}
//建小堆
for (int parent = (k - 1 - 1) / 2; parent >= 0; parent--) //parent就是parent的下标
{
AdjustDown(kmin_heap,k,parent);
}
int val = 0;
while (!feof(fout))
{
fscanf(fout, "%d", &val);
if (val > kmin_heap[0]) //一直与堆顶比
{
kmin_heap[0] = val;
AdjustDown(kmin_heap, k, 0);
}
}
for (int i = 0; i < k; i++)
{
printf("%d\n", kmin_heap[i]);
}
}
int main()
{
CreateNData();
printTopk(10);
return 0;
}
2.堆排序
TopK只是输出了把数据按顺序输出,并没有改变数据内在的顺序。
void AdjustDown(int *a,int size,int parent)
{
int child = parent * 2 + 1; //假设左孩子是小的
while (child < size) //孩子出界了结束
{
//选出左右孩子中小/大的那一个,child+1<size 保证了只有左孩子没有右孩子的情况(防止越界),就是说不用与右孩子作比较了,最小的就是左孩子!
if (child+1<size && a[child + 1] < a[child])
//要是右孩子比左孩子小,那么孩子就是右孩子(= parent * 2 + 2) 大堆:a[child + 1] > a[child]
{
child++;
}
if (a[child] < a[parent]) //大堆: a[child] > a[parent]
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1; //继续假设左孩子是小的
}
else
{
break;
}
}
}
void HeapSort(int *a,int n)
{
/*建堆——向上调整建堆
for (int i = 1; i < n; i++) //第0个可以看做堆,所以从1开始
{
AdjustUp(a, i);
}
*/
//建堆——向下调整建堆
//倒着调整,叶子节点不调整,从倒数第一个非叶子结点(最后一个结点的父亲)开始调整
for (int i = (n-1-1)/2 ; i>=0; i--) //最后一个结点下标是n-1,算他的父亲还要再减一除2, i就是所求的父亲结点的下标
{
AdjustDown(a, n, i);
}
int end = n - 1; //最后一个位置的下标
while (end>0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0); //end同时还是前面的数据个数,因为不把最后一个看到数组内
end--;
}
}
1. 升序——建大堆;降序——建小堆
2. 小堆,选出最小的,首尾交换,最小的放到最后,再把最后一个数据,不看做堆里的元素,向下依次调整选出次小的
3. 向下调整比向上调整更好 ,空间复杂度更低,向上调整的最后一层的复杂度占整个的一半
为什么升序建大堆:如果建小堆的话,每次都要重头开始向下调整建堆,比如你来一个比较小的数,
就每次都要把堆顶替换掉,就会很麻烦,建大堆只需要每次把最大的(堆顶)拿到最后,与新来的交换,
把新来的调整到合适的位置,而不会随便改变原来堆顶所在位置。
到最后每个大的都被拿到后面去了,出来的自然是升序。
3.二叉树
(1)读取顺序以及实现
1.前序
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
printf("%d ", root->data); //打印根(先访问根)
PreOrder(root->left); //再访问左子树
PreOrder(root->right); //再访问右子树
}
2.中序
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
InOrder(root->left); //先访问左子树
printf("%d ", root->data); //再访问根
InOrder(root->right); //再访问右子树
}
3.后序
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
PostOrder(root->left); //先访问左子树
PostOrder(root->right); //再访问右子树
printf("%d ", root->data); //再访问根
}
(2)递归计算结点个数
int BTreeSize(BTNode* root)
{
return NULL ? 0 : (BTreeSize(root->left) + BTreeSize(root->right) + 1);
}
扩展: 递归计算叶子结点个数
//递归计算叶子结点个数
int BTreeLeafSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)
{
return 1;
}
return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);
}
(3) 递归计算二叉树高度
子问题:左子树和右子树高度高的那个加一
//求二叉树高度(用后序,先计算左右,在计算根)
int BTreeHeight(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int leftHeight = BTreeHeight(root->left);
int rightHeight = BTreeHeight(root->right);
return (leftHeight > rightHeight) ? leftHeight+1 : rightHeight+1;
}
(4)第k层结点个数
子问题:转换成统计左子树的第K-1层和右子树的第K-1层的结点个数
结束条件:K==1且结点不为空 或 结点为空
int BTreeLevelKsize(BTNode* root, int k)
{
assert(k > 0);
if (root == NULL)
{
return 0;
}
if (k == 1) //root已经不为空了
{
return 1;
}
return BTreeLevelKsize(root->left, k - 1) + BTreeLevelKsize(root->right, k - 1);
}
(5)查找值为X的结点,返回地址
BTNode* BTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
BTNode* ret1 = BTreeFind(root->left, x);
if (ret1)
{
return ret1;
}
BTNode* ret2 = BTreeFind(root->right, x);
if (ret2)
{
return ret2;
}
return NULL; //到最后也没找到,返回空
}
用前序
(6)层序遍历
//层序遍历
void LevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
{
QueuePush(&q, root);
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
printf("%d ", front->data);
if (front->left)
QueuePush(&q, front->left);
if (front->right)
QueuePush(&q, front->right);
}
printf("\n");
QueueDestroy(&q);
}
(7)二叉数销毁(用后序)
void BTreeDestory(BTNode* root)
{
if (root == NULL)
return;
BTreeDestory(root->left);
BTreeDestory(root->right);
free(root);
}
//因为销毁根之前要保存左右子树,所以最好用后序
(8)判断是否为完全二叉树(用队列)
当出队列的时候让其左右子树进队列。
bool BTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
while (!QueueEmpty(&q)) //进数据
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
//遇到空就跳出
if (front == NULL)
{
break;
}
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
//检查后面的结点有没有非空,完全二叉树空之后不会再有非空节点
while (!QueueEmpty(&q)) //出数据
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front != NULL)
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true; //走到这里说明是完全二叉树了
}
完全二叉树应该是NULL之后全是NULL了;
(9)性质
五.排序
1.插入排序
(1)直接插入排序
void InsertSort(int* a,int n)
{
for (int i = 1; i < n; i++)
{
// [0,end] 有序,插入tmp之后依然有序
int end = i - 1; //从后往前比更好
int tmp = a[i]; //tmp是end的后面一个数
while (end >= 0) //找到空
{
if (tmp < a[end])
{
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
a[end + 1] = tmp;
}
}
插入排序优于冒泡排序
(2)希尔排序
void ShellSort(int* a, int n)
{
int gap = n;
//1.当gap > 1时,预排序
//2.当gap = 1时,直接插入排序
while (gap > 1) //不能大于等于,不然最后一直死循环
{
gap = gap / 3 + 1; //+1可以保证最后一次循环的时候gap=1
//gap = gap / 2 也可以
//多组并排,效率跟下面的二层循环是一样的
for (int i = 0; i < n - gap; i++) //每一(gap)组,i < n - gap是为了防止越界
{
int end = i;
int tmp = a[end + gap]; //要新插入的(要比较的)是end后面的第gap个
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end = end - gap;
//end被减是为了要跳出循环,要不然之后直接写 a[end] = tmp 了,也不需要a[end + gap] = tmp 了
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
gap越大,跳的越快,大的数可以更快的到后面,小的数可以更快的到前面,
但是gap越大预排序越不接近有序;
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int j = 0; j < gap; j++) //分gap组
{
for (int i = j; i < n - gap; i = i + gap) //每一(gap)组,i < n - gap是为了防止越界
{
int end = i;
int tmp = a[end + gap]; //要新插入的(要比较的)是end后面的第gap个
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end = end - gap;
//end被减是为了要跳出循环,要不然之后直接写 a[end] = tmp 了,也不需要a[end + gap] = tmp 了
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
}
希尔更优。显示的时运行时间(ms)
*希尔排序的时间复杂度:O(N^1.3)
2.选择排序
(1)直接选择排序
void Swap(int* p1,int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void SelectSort(int* a, int n)
{
int begin = 0, end = n - 1;
while (begin < end)
{
int max_i = begin, min_i = begin;
for (int i = begin; i <= end; i++) //找出较大的和较小的
{
if (a[i] > a[max_i])
{
max_i = i;
}
if (a[i] < a[min_i])
{
min_i = i;
}
}
Swap(&a[begin], &a[min_i]);
//如果max_i与begin重叠,修正一下
if (begin == max_i)//现在已经交换完了,大的数来到了min_i的位置
{
max_i = min_i; //那么就让max_i修正到min_i的位置
}
Swap(&a[end], &a[max_i]);
begin++;
end--;
}
}
每遍历一趟,选出最小的数和最大的数,与最左边,最右边交换;
(2)堆排序
void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void AdjustDonw(int* a, int n,int parent)
{
int child = parent * 2 + 1; //假设左孩子是小的
while (child < n)
{
if (child + 1 < n && a[child + 1] < a[child]) //左孩子加一就是右孩子
{
child = child + 1;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else //不用调了
{
break;
}
}
}
void HeapSort(int* a, int n)
{
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDonw(a, n, i); //从结点i这个位置开始向下调整
}
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDonw(a, end, 0);
end--;
}
}
升序建大堆,降序建小堆
3.交换排序
(1)冒泡排序
一般,不讲了。
(2)快速排序
1.介绍
2.key的位置选取
3.递归过程
4. 代码
1. hoare版本
int PartSort(int* a,int left,int right) //单趟排序
{
int key = left;
while (left < right)
{
//右边找到小与key的
while (left<right && a[right] >= a[key]) //要先检查有没有越界
{
right--;
}
//左边找到大于key的
while (left < right && a[left] <= a[key])
{
left++;
}
//交换left与right,让大于key的去右边,小于key的去左边
Swap(&a[left], &a[right]);
}
//当相遇时,交换相遇点和key
Swap(&a[left], &a[key]);
return left; //left与key交换了位置,别返回key了
//因为之后递归要划分区间,所以返回的表示下标的那个
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end) //结束条件
{
return;
}
int key = PartSort(a, begin, end);
//之后被分成了三个区间:[begin,key-1], key, [key+1,end]
QuickSort(a, begin, key - 1); //对左区间递归 //不要把quicksort与partsrot的递归弄混了
QuickSort(a, key + 1, end); //对右区间递归
}
2. 挖坑法
int PartSort2(int* a, int left, int right)
{
int key = a[left];
int hole = left; //key记录值,坑记录下标
while (left < right)
{
//右边找到小与key的
while (left < right && a[right] >= key) //要先检查有没有越界
{
right--;
}
a[hole] = a[right]; //找到小的之后,把小的填到坑里
hole = right; //再把那个新位置标记为坑
//左边找到大于key的
while (left < right && a[left] <= key)
{
left++;
}
a[hole] = a[left]; //找到大的之后,把大的填到坑里
hole = left; //再把那个新位置标记为坑
}
a[hole] = key; //最后一个坑就是key的位置
return hole; //返回的是hole,不是key
//因为之后递归要划分区间,所以返回的表示下标的那个
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end) //结束条件
{
return;
}
int key = PartSort2(a, begin, end);
//之后被分成了三个区间:[begin,key-1], key, [key+1,end]
QuickSort(a, begin, key - 1); //对左区间递归 //不要把quicksort与partsrot的递归弄混了
QuickSort(a, key + 1, end); //对右区间递归
}
3.前后指针法
int PartSort3(int* a, int left, int right)
{
int prev = left;
int cur = left + 1;
int key = left; //key在这里是下标
while (cur <= right)
{
if (a[cur] < a[key] )
{
prev++;
Swap(&a[prev], &a[cur]);
}
cur++;
}
Swap(&a[prev], &a[key]);
key = prev;
return key; //也可以直接return prve,(区间中间的下标)
}
1.最开始prev和cur是相邻的
2.当cur遇到比key大的值以后,此时prev与cur之间的值都是比key大的
3.cur找小,找到小的以后,跟++prev位置的值交换,相当于把大的丢后面,小的放前面了
void QuickSort(int* a, int begin, int end)
{
if (begin >= end) //结束条件
{
return;
}
int key = PartSort3(a, begin, end);
//之后被分成了三个区间:[begin,key-1], key, [key+1,end]
QuickSort(a, begin, key - 1); //对左区间递归 //不要把quicksort与partsrot的递归弄混了
QuickSort(a, key + 1, end); //对右区间递归
}
5.效率分析
当每次key都取中位数的时候效率最高 ,为N*logN;
当数组接近有序的时候效率最低,为N^2; (因为每次key都只能往后取一到两个)
解决方案:1.随机数选key
2. 三个数中取不大不小的那个作为key
//例子:前后指针法的改进
//三数取中——取的是下标
int GetMidIndex(int* a, int left, int right)
{
int mid = (left + right) / 2;
if (a[left] < a[mid])
{
if (a[mid] <= a[right]) //1 2 3
{
return mid; //left mid right ,mid是中位数
}
else if (a[left] < a[right]) //1 3 2
{
return right;
}
else //2 3 1
{
return left;
}
}
else // a[left] > a[mid]
{
if (a[mid] > a[right]) //3 2 1
{
return mid;
}
else if (a[right] > a[left]) //2 1 3
{
return left;
}
else //3 1 2
{
return right;
}
}
}
int PartSort3(int* a, int left, int right)
{
int mid = GetMidIndex(a, left, right); //三数取中
Swap(&a[left], &a[mid]);
int prev = left;
int cur = left + 1;
int key = left; //key在这里是下标
while (cur <= right)
{
if (a[cur] < a[key] )
{
prev++;
Swap(&a[prev], &a[cur]);
}
cur++;
}
Swap(&a[prev], &a[key]);
key = prev;
return key; //也可以直接return prve,(区间中间的下标)
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end) //结束条件
{
return;
}
int key = PartSort3(a, begin, end);
//之后被分成了三个区间:[begin,key-1], key, [key+1,end]
QuickSort(a, begin, key - 1); //对左区间递归 //不要把quicksort与partsrot的递归弄混了
QuickSort(a, key + 1, end); //对右区间递归
}
6.用栈模拟递归:也是N*logN
void QuickSortNonR(int* a, int begin,int end) //非递归快排
{
ST st;
StackInit(&st);
StackPush(&st, end);
StackPush(&st, begin); //等会先出begin
while (!StackEmpty(&st))
{
int left = StackTop(&st); //先出begin
StackPop(&st);
int right = StackTop(&st);
StackPop(&st);
int key = PartSort3(a, left, right);
//分割出了三段区间 [left,key-1] key [key+1,right]
//入栈先入后区间,这样前区间后进先出
if (key + 1 < right) //判断区间合法
{
StackPush(&st, right); //跟上面一样,先压入区间的end
StackPush(&st, key+1);
}
if (left < key - 1)
{
StackPush(&st, key - 1);
StackPush(&st, left);
}
}
StackDestroy(&st);
}
用队列模拟:
7.三路划分的优化
防止的问题:
思路:
//快排的三路划分
void QuickSort(int* a, int begin, int end)
{
if (begin == end)
{
return;
}
int left = begin;
int right = end;
int cur = left + 1;
int mid = GetMidIndex(a, left, right);
Swap(&a[left], &a[mid]);
int key = a[left]; //让key尽量取中间值
while (cur <= right)
{
if (a[cur] < key)
{
Swap(&a[left], &a[cur]);
left++;
cur++;
}
else if (a[cur] > key)
{
Swap(&a[right], &a[cur]);
right--; //换过来的不知道大于还是小于key,所以不用cur++
}
else //cur == key
{
cur++;
}
}
//排完之后的结果:[begin,left-1],[left,right],[right+1,end]
QuickSort(a, begin, left - 1); //递归左边和右边
QuickSort(a, right + 1, end);
}
4.归并排序
(1)介绍![](https://i-blog.csdnimg.cn/blog_migrate/7ae2077634ffa62ae89b957a1d7e502a.png)
(2)代码
void _MergeSort(int* a, int begin,int end,int* tmp)
{
//递归结束条件
if (begin == end)
{
return;
}
int mid = (begin + end) / 2;
//分成了两个区间: [begin,mid] [mid+1,end]
_MergeSort(a, begin, mid, tmp); //对前后区间分别排,然后归并
_MergeSort(a, mid+1, end, tmp);
//每组的归并排序
int begin1 = begin, end1 = mid;
int begin2 = mid + 1, end2 = end;
int i = begin;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
tmp[i++] = a[begin1++];
}
else //a[begin1] >= a[begin2]
{
tmp[i++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2) //这两个里面只会进去一个
{
tmp[i++] = a[begin2++];
}
memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1)); //再给他拷贝回去
//之所以 a+begin, tmp+begin 是因为a到tmp应该是一个区间,而不加上begin就不是一个区间了
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
_MergeSort(a, 0, n - 1, tmp); //还是用区间
free(tmp);
}
时间复杂度:N*logN 高度是logN,每一次归并O(N),所以是N*logN
(3)非循环:用gap的二倍依次乘(2个,4个....16个)
gap导致的数组越界:
解决:
法一:归并一组,拷贝一组
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
int gap = 1; //gap为每组的数据个数
while (gap < n)
{
int j = 0;
for (int i = 0; i < n; i = i + 2 * gap) //i每加两组gap,跳到下两组,这整个for循环是一个gap为 的归并排序,拷贝操作应该在外面
{
int begin1 = i, end1 = i + gap - 1; //从i开始,到i+gap结束,这个结束的下标是i+gap-1
int begin2 = i + gap, end2 = i + gap * 2 - 1;
//第一种,第二种情况
if (end1 >= n || begin2 >= n)
{
break;
}
//第三种情况,修正的思路
if (end2 >= n)
{
end2 = n - 1;
//end2越界,那么end2之后的都没用了,直接让end2等于n-1(最后一个的下标)
}
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[j++] = a[begin1++];
}
else //a[begin1] >= a[begin2]
{
tmp[j++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[j++] = a[begin1++];
}
while (begin2 <= end2) //这两个里面只会进去一个
{
tmp[j++] = a[begin2++];
}
//归并一组,拷贝一组,不归并的就直接按原样放着
memcpy(a+i , tmp+i , sizeof(int) * (end2 - i + 1));
//i是起始位置,end2是结束位置,end2-i就是差值,再加一就是个数
}
gap = gap * 2;
}
free(tmp);
}
法二:先修正,再整体拷贝
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
int gap = 1; //gap为每组的数据个数
while (gap < n)
{
int j = 0;
for (int i = 0; i < n; i = i + 2 * gap) //i每加两组gap,跳到下两组,这整个for循环是一个gap为 的归并排序,拷贝操作应该在外面
{
int begin1 = i, end1 = i + gap - 1; //从i开始,到i+gap结束,这个结束的下标是i+gap-1
int begin2 = i + gap, end2 = i + gap * 2 - 1;
//修正:
if (end1 >= n)
{
end1 = n - 1;
//end1越界,那么end1之后的越界了,直接让end1等于n-1(最后一个的下标)
begin2 = n ; //为了不让这一段区间成立,所以要修改成不存在的区间
end2 = n - 1;
}
else if (begin2 >= n)
{
begin2 = n ; //为了不让这一段区间成立,所以要修改成不存在的区间
end2 = n - 1;
}
else if (end2 >= n)
{
end2 = n - 1;
}
//第三种情况,修正的思路
if (end2 >= n)
{
end2 = n - 1;
//end2越界,那么end2之后的都没用了,直接让end2等于n-1(最后一个的下标)
}
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[j++] = a[begin1++];
}
else //a[begin1] >= a[begin2]
{
tmp[j++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[j++] = a[begin1++];
}
while (begin2 <= end2) //这两个里面只会进去一个
{
tmp[j++] = a[begin2++];
}
}
memcpy(a , tmp , sizeof(int) * n);
gap = gap * 2;
}
free(tmp);
}
(4)归并的小区间优化(针对递归)
void _MergeSort(int* a, int begin,int end,int* tmp)
{
//递归结束条件
if (begin == end)
{
return;
}
//判断小区间:小区间用递归太浪费,用个小排序就行
if (end - begin + 1 < 10)
{
InsertSort(a+begin, end - begin + 1);
return;
}
int mid = (begin + end) / 2;
//分成了两个区间: [begin,mid] [mid+1,end]
_MergeSort(a, begin, mid, tmp); //对前后区间分别排,然后归并
_MergeSort(a, mid+1, end, tmp);
//每组的归并排序
int begin1 = begin, end1 = mid;
int begin2 = mid + 1, end2 = end;
int i = begin;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i++] = a[begin1++];
}
else //a[begin1] >= a[begin2]
{
tmp[i++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2) //这两个里面只会进去一个
{
tmp[i++] = a[begin2++];
}
memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1)); //再给他拷贝回去
//之所以 a+begin, tmp+begin 是因为a到tmp应该是一个区间,而不加上begin就不是一个区间了
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
_MergeSort(a, 0, n - 1, tmp); //还是用区间
free(tmp);
}
5.计数排序
(1)
(2)
void CountSort(int* a, int n)
{
int k = 0;
int min = a[0], max = a[0];
for (int i = 0; i < n; i++)
{
if (a[i] < min)
{
min = a[i];
}
if (a[i] > max)
{
max = a[i];
}
}
int range = max - min + 1; //相对映射
int* countA = (int*)malloc(sizeof(int) * range);
memset(countA, 0, sizeof(int) * range);
//统计次数
for (int i = 0; i < n; i++)
{
countA[a[i] - min]++;
}
//排序——遍历计数数组,不是原数组
for (int j = 0; j < range; j++)
{
while (countA[j]--)
{
a[k++] = min + j;
}
}
}