线性表总结
线性表:线性表是由n个数据特性相同的元素组成的有限序列。它是学习其他数据结构的基础。线性表在计算机中可以用顺序存储和链式存储两种存储结构来表示。其中,用顺序存储结构表示的是顺序表,用链式存储结构表示的是链表。(链表又有单链表,双向链表,循环链表之分)
一些前提知识:
1,因为以后可能会对代码进行改变,所以可以提前定义好一些后期可能会变的量。
比如:数组的大小,arr[100]。那么可以在开头#define N 100.
比如:数组的数据类型,int arr[10]。那么可以在开头typedef int SQDataType
这样以后要改的话,就直接在宏定义上进行修改就可以了。
(类型的定义就用typedef,变量的定义就用define)
2,对线性表进行增删改查的时候用的都是接口函数。
3,结构体的定义:
结构体的定义:
Struct seq
{
};
还有简便的是:
Typedef struct seq
{
}S;
Typedef struct seq S
{
};
这样都把原来的名字改成了自己想用的名字。
一,顺序表
顺序存储结构,是指用一段地址连续的存储单元依次存储线性表的数据元素。实际上我们是用数组来实现这种结构的。顺序表又分为静态顺序表和动态顺序表。静态顺序表的容量大小在开始时就是已经定义好了的。而动态顺序表的容量大小则是可以改变的。(本文中代码实现的是动态顺序表)
静态顺序表的缺点:容量必须在一开始定义好,如果定义少了不够用,如果定义多了用不完浪费,不能灵活控制。
1,头文件
可以先将头文件写好,这个头文件也就是起一个将条件准备完整的作用,
定义好简便的,可以及时修改的符号变量,(常变量是用const来修饰的)
定义好顺序表的结构体(类型名,数组的类型,大小)
定义好要等会要用的接口。然后在C文件中进行解释说明就行了。
代码实现:
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<assert.h>
#include<stdio.h>
#include<string.h>
#include<malloc.h>
#include<stdlib.h>
typedef int SQDataType;
typedef struct SeqList
{
SQDataType* a;
int size;
int capacity;
}SL;
void SeqListInit(SL* ps);
void SeqListPrint(SL* ps);
void SeqListDestroy(SL* ps);
void SeqListPushFront(SL* ps, SQDataType x);
void SeqListPushBack(SL* ps, SQDataType x);
void SeqListPopFront(SL* ps);
void SeqListPopBack(SL* ps);
void SeqLisInsert(SL* ps, int pos, SQDataType x);
void SeqListErase(SL* ps, int pos);
int SeqListFind(SL* ps,SQDataType x);
void SeqListModify(SL* ps, int pos, SQDataType x);
void SeqListCheckCapacity(SL* ps);
2,C文件
c文件中主要是h中的接口的定义。
代码实现:
#include "SeqList.h"
void SeqListInit(SL* ps)
{
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
void SeqListCheckCapacity(SL* ps)
{
if (ps->size >= ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SQDataType* tmp = (SQDataType*)realloc(ps->a, newcapacity * sizeof(SQDataType));
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
else
{
ps->a = tmp;
ps->capacity = newcapacity;
}
}
}
void SeqListPushBack(SL* ps, SQDataType x)
{
SeqListCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
void SeqListPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
void SeqListPushFront(SL* ps, SQDataType x)
{
SeqListCheckCapacity(ps);
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[0] = x;
ps->size++;
}
void SeqListPopBack(SL* ps)
{
assert(ps->size > 0);
ps->size--;
}
void SeqListPopFront(SL* ps)
{
assert(ps->size > 0);
int start = 1;
while (start < ps->size)
{
ps->a[start - 1] = ps->a[start];
start++;
}
ps->size--;
}
void SeqListInsert(SL* ps, int pos, SQDataType x)
{
assert(pos<ps->size);
SeqListCheckCapacity(ps);
int end = ps->size - 1;
while (pos <= end)
{
ps->a[end +1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
void SeqListErase(SL* ps,int pos)
{
assert(pos < ps->size);
int start = pos + 1;
while (start < ps->size)
{
ps->a[start - 1] = ps->a[start];
start++;
}
ps->size--;
}
void SeqListDestroy(SL* ps)
{
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
int SeqListFind(SL* ps, SQDataType x)
{
for (int i= 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
void SeqListModify(SL* ps, int pos, SQDataType x)
{
assert(pos < ps->size);
ps->a[pos] = x;
}
3,测试菜单文件(menu)
写了个相关的小菜单,实现了一点人机交互。
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h";
void menu()
{
printf("************************************\n");
printf("1,尾插数据, 2,头插数据\n");
printf("3,尾删数据, 4,头删数据\n");
printf("5,在指定位置删数据, 6,在指定位置删除数据\n");
printf("7,查找数据, 8,打印数据\n");
printf("9,销毁表格, 0,修改表格\n");
printf(" -1,退出\n");
printf("请输入你的操作:\n");
printf("************************************\n");
}
int main()
{
SL sl;
SeqListInit(&sl);
int option = 0;
int x = 0;
while (option != -1)
{
menu();
scanf("%d", &option);
switch (option)
{
case 1:
printf("请输入你要在表头插入的数据,以-1结束\n");
do
{
scanf("%d", &x);
if (x != -1)
SeqListPushBack(&sl, x);
} while (x != -1);
printf("已插入******\n");
break;
case 2:
printf("请输入你要在表头插入的数据,以-1结束\n");
do
{
scanf("%d", &x);
if (x != -1)
{
SeqListPushFront(&sl, x);
}
} while (x != -1);
printf("已插入******\n");
break;
case 3:
printf("您确定要删除表尾的数据吗?1:确定||2:否定\n");
int num1 = 0;
scanf("%d", &num1);
if (num1 == 1)
{
SeqListPopBack(&sl);
printf("已经删除******\n");
break;
}
else
break;
case 4:
printf("您确定要删除表头的数据吗?1:确定||2:否定\n");
int num2 = 0;
scanf("%d", &num2);
if (num2 == 1)
{
SeqListPopFront(&sl);
printf("已经删除******\n");
break;
}
else
break;
break;
case 5:
printf("请输入您要插入元素的位置\n");
int num5 = 0;
scanf("%d", &num5);
printf("您要插入的数据是:\n");
SQDataType x;
scanf("%d", &x);
SeqListInsert(&sl, num5,x);
printf("已插入******\n");
break;
case 6:
printf("请输入您要删除元素的位置\n");
int num6 = 0;
scanf("%d", &num6);
SeqListErase(&sl, num6);
printf("已删除******\n");
break;
case 7:
printf("请输入你要查找的数据:\n");
int num3 = 0;
scanf("%d", &num3);
int num4 = SeqListFind(&sl, num3);
if (num4 != -1)
{
printf("表中确实存在该数据,它的下标是:%d\n", num4);
break;
}
else
printf("抱歉,表中不存在该数据\n");
break;
case 8:
SeqListPrint(&sl);
break;
case 9:
printf("您确定要销毁表格吗?1:确定||2:否定\n");
int num9 = 0;
scanf("%d", &num9);
if (num9 == 1)
{
SeqListDestroy(&sl);
printf("已销毁******");
break;
}
else
break;
case 0:
printf("请输入您要修改的数据的位置:\n");
int num0 = 0;
scanf("%d", &num0);
printf("您要修改为的数据是:");
SQDataType x1;
scanf("%d", &x1);
SeqListModify(&sl, num0, x1);
printf("修改完毕******");
break;
case -1:
break;
default:
break;
}
}
SeqListDestroy(&sl);
return 0;
}
4,顺序表的优缺点
优点:
1,无须为表示表中元素逻辑关系而增加额外的储存空间。
2,随机存取元素时可以达到O(1),效率高。
缺点:
1,插入和删除数据的时候需要移动大量的元素。
2,必须一开始就确定存储空间的容量。
3,如果空间不够了,增容。增容会付出一定性能的消耗,其次可能存在一定的空间浪费。(动态顺序表)
二,单链表
具体操作有:打印,尾插,头插,尾删,头删,在任意结点之前插入,删除任意结点,malloc一个新结点,在所给链表中查找数据x,并返回它的结点等。
标注的比较详细,可以借助注释食用哦。
分为三个项:(头文件,C文件,测试文件)
SList.h:包的引用和函数的声明
SList.c:各个操作的实现
Test.c:各个操作的实现
1,头文件
代码实现:
#pragma once
#include<stdio.h>
#include<stdlib.h>
typedef int SLTDataType;
struct SListNode
{
SLTDataType data;
struct SListNode* next;
};
typedef struct SListNode SLTNode;
void SListPrint(SLTNode* phead);
void SListPushBack(SLTNode** pphead,SLTDataType);
void SListPushFront(SLTNode** pphead, SLTDataType x);
void SListPopBack(SLTNode** pphead);
void SListPopFront(SLTNode** pphead);
void SListInsert(SLTNode** pphead, int pos, SLTDataType x);
void SListErase(SLTNode** pphead, int pos);
SLTNode* BuySListNode(SLTDataType x);
SLTNode* SListFind(SLTNode*phead, SLTDataType x);
2,C文件
代码实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
SLTNode* BuySListNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuySListNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuySListNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
void SListPopFront(SLTNode** pphead)
{
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
void SListPopBack(SLTNode** pphead)
{
SLTNode* prev = NULL;
SLTNode* tail = *pphead;
if (*pphead == NULL)
{
return;
}
else if ((*pphead)->next==NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
prev->next = NULL;
}
}
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur != NULL)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
void SListInsert(SLTNode** pphead,SLTNode* pos,SLTDataType x)
{
SLTNode* newnode = BuySListNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
newnode->next = NULL;
}
else if (pos == *pphead)
{
newnode->next = *pphead;
*pphead = newnode;
}
else
{
SLTNode* tmpt = *pphead;
while (tmpt->next != pos)
{
tmpt = tmpt->next;
}
tmpt->next = newnode;
newnode->next = pos;
}
}
void SListErase(SLTNode** pphead,SLTNode*pos)
{
SLTNode* cur = *pphead;
if (*pphead == NULL)
{
return;
}
else if(pos==*pphead)
{
*pphead = cur->next;
free(cur);
cur = NULL;
}
else
{
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = pos->next;
free(pos);
}
}
3,测试文件
这个单链表可以进行测试测试。
#pragma once
#include"SList.h"
test1()
{
SLTNode* phead = NULL;;
SListPushBack(&phead, 1);
SListPushBack(&phead, 2);
SListPushBack(&phead, 3);
SListPrint(phead);
SLTNode* pos = SListFind(phead, 1);
if (pos != NULL)
{
SListInsert(&phead, pos, 0);
SListPrint(phead);
}
else
printf("没有找到您要找的位置");
SLTNode* pos2 = SListFind(phead,2);
if (pos2 != NULL)
{
SListErase(&phead, pos2);
SListPrint(phead);
}
else
{
printf("没有找到您要删除的元素");
}
}
int main()
{
test1();
return 0;
}
4,带头链表和不带头链表
上面的单链表是属于不带头的单链表。接下来讲一下带头的单链表。
带头链表的好处:
尾插:还要判断首结点是不是空,如果是空,那么还得改变头指针的指向(原来是NULL,现在要指向这个newnode,修改了),所以这个就需要二级指针了。(如果要对原先的头指针进行修改,那么就需要使用二级指针。),而带头链表直接在head后面加就行了。不需要使用二级指针。尾插的判断会更简单。
头删:也是一样的,普通的单链表要找到d2,让头指针指向d2才行,这样也需要改变plist,使用二级指针。但是带头链表,指针直接找到d2了。(将d2的地址保存到head的next中)直接让head上的头指针指向d2就行了。这样实际上也没有改变头指针的地址。注意:头指针和带头结点是不一样的,头指针只有一个地址,没有next,而带头结点是有next的,这就可以放直接放地址,直接指向一个地方而不用改变自身的地址。而头指针改变指向的对象的话,就会改变自身的地址。
注意:头结点的head是不能存数值的(不能用头结点来存链表的长度)。这样是不规范的。链表中存的都是整数还好说,如果是其他的数据类型,例如char,double就尴尬了。
单链表的尾删,插入,删除的时间复杂度都是O(n),他们都是需要找到指定节点的前一个结点。
解决方案:双向链表(有后继,有前驱)
三,双向链表
用代码实现一下带头的双向循环链表。
1,头文件
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#pragma once
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
int data;
}ListNode;
ListNode* ListInit();
void ListDestory(ListNode* plist);
void ListPushBack(ListNode* plist, LTDataType x);
ListNode* BuyListNode(LTDataType x);
void ListPrint(ListNode* plist);
void ListPushFront(ListNode* plist, LTDataType x);
void ListPopFront(ListNode* plist);
void ListPopBack(ListNode* plist);
ListNode* ListFind(ListNode* plist, LTDataType x);
ListNode* ListInsert(ListNode* pos,LTDataType x);
ListNode* ListErase(ListNode* pos);
void ListModify(ListNode* pos, int num);
2,C文件
#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
ListNode* BuyListNode(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
newnode->next = NULL;
newnode->data = x;
return newnode;
}
ListNode* ListInit()
{
ListNode* phead =BuyListNode(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
void ListPushBack(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* newnode = (ListNode*)BuyListNode(x);
ListNode* tail = phead->prev;
newnode->prev = tail;
newnode->next = phead;
tail->next = newnode;
phead->prev = newnode;
}
void ListPrint(ListNode* phead)
{
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
void ListPushFront(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* newnode = BuyListNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
void ListPopFront(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListNode* first = phead->next;
ListNode* second = phead->next->next;
phead->next = second;
second->prev = phead;
free(first);
first = NULL;
}
void ListPopBack(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListNode* tail = phead->prev;
ListNode* tailPrev = phead->prev->prev;
tailPrev->next = phead;
phead->prev = tailPrev;
free(tail);
tail = NULL;
}
ListNode* ListFind(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* cur = phead->next;
while (cur->data != x)
{
cur = cur->next;
if (cur->next == phead)
return NULL;
}
return cur;
}
void ListModify(ListNode* pos, int num)
{
pos->data = num;
}
ListNode* ListInsert(ListNode* pos, int x)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* newnode = BuyListNode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = prev;
}
ListNode* ListErase(ListNode* pos)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
pos = NULL;
}
void ListDestory(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
phead = NULL;
}
四,栈
1,栈的相关概念
(1),定义
栈是一种特殊的线性表,只允许在固定的一端进行插入和删除元素,进行数据插入和删除操作的一段称为栈顶,另一端称为栈底。
(2),实现方式的选择
用数组实现:
用数组实现栈可以完美的避开顺序表的缺点(避免在表头插入和删除需要移动大量的数据,因为如果用数组来实现栈的话,就是将顺序表的尾巴当作的栈顶,压栈和出栈对应着尾插和尾删,这就很方便)(而且数据扩容也不是特别的频繁。)
用链表实现:
1,双链表:链尾表示栈顶,如果要删除的话,必须要找到最后一个还有倒数第二个结点,也还可以。
2,单链表:链头表示栈顶,开始的时候,让栈顶和栈底都指向同一个,压栈就是头插,出栈就是头删。举个头删的例子:保存栈顶的后一个,然后将原先的栈顶销毁掉,让栈顶指向栈顶的后一个。这样出栈和入栈都是O(1)。
所以如果用链尾表示栈顶,那么就用双链表。
如果用链头表示栈顶,那么就用单链表。
这些方法都是可以的,你能写出来就行。
推荐:数组
1,(单链表增和删有些繁琐)
2,数组的cpu缓存的命中率要更高一点。(预加载)(因为数组的地址是连续的)
如果是连续的内存,cpu要访问的话要把他加载到寄存器。我会直接预加载一大块东西。第一个命中了,第二个命中的概率就会很多高了。链表就不会了,命中了第一个,由于内存不是连续的,所以想要预加载到第二个就会有些困难。
2,头文件
#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<assert.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}Stack;
void StackInit(Stack* ps);
void StackDestory(Stack* ps);
void StackPop(Stack* ps);
void StackPush(Stack* ps, STDataType x);
STDataType StackTop(Stack* ps);
int StackSize(Stack* ps);
bool StackEmpty(Stack* ps);
3,C文件
#include"Stack.h"
void StackInit(Stack* ps)
{
assert(ps);
if (ps->a == NULL)
{
printf("malloc失败了");
exit(-1);
}
ps->a = (Stack*)malloc(sizeof(STDataType) * 4);
if (ps->a == NULL)
{
printf("malloc失败了");
exit(-1);
}
ps->capacity = 4;
ps->top = 0;
}
void StackDestory(Stack* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
void StackPush(Stack* ps,STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
if (tmp == NULL)
{
printf("realloc失败了");
exit(-1);
}
else
{
ps->a = tmp;
ps->capacity = 2 * ps->capacity;
}
}
ps->a[ps->top] = x;
ps->top++;
}
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--;
}
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
return ps->a[ps->top - 1];
}
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
bool StackEmpty(Stack* ps)
{
return ps->top == 0;
}
4,测试文件
#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
int main()
{
Stack st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
while (!StackEmpty(&st))
{
printf("%d ", StackTop(&st));
StackPop(&st);
}
printf("\n");
StackDestory(&st);
}
五,队列
1,队列的相关概念
(1),定义
队列定义:队列是只允许在一端插入数据,然后在另一端进行删除数据操作的特殊线性表。队列的特点就是先进先出。
在队头出数据,在队尾进数据。
(2),实现方式的选择
实现方法:
数组:不太好,因为队头出数据的时候需要挪动了链表。
链表:操作就是头删和尾插。(完美的运用的单链表的优点,头删和尾插的效率都很高)。
下面的代码定义了一个(指针)结构体{head和tail},这样就不用二级指针了。
这里是不需要带头结点的,双向链表中的带头结点的目的是解决单链表中的二级指针的问题的,带头结点中的next和prev可以存取地址。头指针和带头结点是不一样的,头指针只有一个地址,没有next,而带头结点是有next的,这就可以放直接放地址,直接指向一个地方而不用改变自身的地址。而头指针改变指向的对象的话,就会改变自身的地址。(单链表和双向链表)
而队列:有指针结构体,在一个结构体存储着两个指针,一个是head还有一个是tail指针。有这两个指针也就不需要改变头结点的指针了,也就不需要二级指针了。
注意有两个结构体,一个结构体是关于指针的结构体,还有一个结构体是关于结点的结构体。如果向函数中传的参数是结点的话,那么就需要用二级指针,但是传的参数是指针的话,就只需要传指针就行了。
那为什么单链表的时候不用这个结构体来用tail呢?有tail之后尾插确实很简单,但是尾删还是很困难。单链表就是直接传的head,进行二级指针,这样就能改变指针的值。
1,头文件
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<assert.h>
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
}Queue;
void QueueInit(Queue* pq);
void QueueDestory(Queue* pq);
void QueuePop(Queue* pq);
void QueuePush(Queue* pq, QDataType x);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);
2,C文件
#include"Queue.h"
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->head);
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
}
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
printf("mallco fail\n");
exit(-1);
}
newnode->next = NULL;
newnode->data = x;
if (pq->tail == NULL)
{
pq->tail = pq->head = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->head);
return pq->head->data;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->head);
return pq->tail->data;
}
int QueueSize(Queue* pq)
{
int size = 0;
QNode* cur = pq->head;
while (cur)
{
size++;
cur = cur->next;
}
return size;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL;
}
void QueueDestory(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
}
3,测试文件
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"Queue.h"
#include"Queue.h"
int main()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePop(&q);
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
printf("\n");
return 0;
}
六,栈和队列的补充
栈的实现:
底层可以用数组,也可以用链表,可以单链表,双链表,带头,不带头都可以。
如果用链表实现的话,推荐用单链表。
用链表的头这一端来做栈顶。这样录数据就相当于头插,出数据就相当头删。O(1)。
不要用链表的尾这一端来做栈顶,这样录数据好进入。但是出数据的时候就不容易删除了(要找到它的前一个,必须要遍历)O(n).
但是:
更推荐用数组来实现。录入数据就是直接将数据赋值过去,然后top下标向后移动就行了。出数据就直接top–,最后的数据就没有了(两个操作都是在数组的末尾进行的)可以用动态的数组,也可以用静态的数组。但是推荐用动态的,静态的给多了用不完,给少了不够用。
也就是在定义结构体的时候多一个capacity,然后不够用了就扩容。realloc。
队列的实现:
推荐还是用链表,
用数组的话就不太好:录入数据的时候还好说,但是出数据的时候出一个数据就需要将后面的数据整体的往前移动。效率就低了。
用链表:从链表的尾处入数据,在链表的头处出数据。(两个指针)都是O(1),把优点集合两个了。(三个方向都挺好的,在头上插,在头上删,在尾上插。但是在尾上删除就不好了O(n))。
七,二叉树
1,树的概念
结构:任何一棵二叉树都有三部分,根结点,左子树,右子树。
重点算法:分治算法:分而治之,大问题分成类似子问题,子问题再分成子问题。直到子问题不可再分割。
遍历方法:
前序(先根遍历):先访问根结点,然后左子树,最后右子树。
中序(中根遍历):先访问左子树,然后根结点,最后右子树。
后序(后根遍历):先访问左子树,然后右子树,最后根节点。
满二叉树:每一层都是满的。
设总的结点数是N个,共h层。那么定满足2^h-1=N.
完全二叉树: 设树的高度是h,则h-1层都是满的,最后一层不是满的,但是最后一层从左到右都是连续的。
设最后一层还差x个结点才能构成满二叉树,那么满足2^h-1-x=N。
2,前序,中序,后序遍历
代码实现:(为了演示三种遍历顺序,在main函数中直接定义了一棵二叉树。(具体见下面main函数))
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
typedef char BTDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
BTDataType data;
}BTNode;
void PrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%c ", root->data);
PrevOrder(root->left);
PrevOrder(root->right);
}
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%c ", root->data);
InOrder(root->right);
}
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%c ", root->data);
}
int main()
{
BTNode* A = (BTNode*)malloc(sizeof(BTNode));
A->data = 'A';
A->left = NULL;
A->right = NULL;
BTNode* B = (BTNode*)malloc(sizeof(BTNode));
B->data = 'B';
B->left = NULL;
B->right = NULL;
BTNode* C = (BTNode*)malloc(sizeof(BTNode));
C->data = 'C';
C->left = NULL;
C->right = NULL;
BTNode* D = (BTNode*)malloc(sizeof(BTNode));
D->data = 'D';
D->left = NULL;
D->right = NULL;
BTNode* E = (BTNode*)malloc(sizeof(BTNode));
E->data = 'E';
E->left = NULL;
E->right = NULL;
A->left = B;
A->right = C;
B->left = D;
B->right = E;
PrevOrder(A);
printf("\n");
InOrder(A);
printf("\n");
PostOrder(A);
return 0;
}
输出结果:(前序,中序,后序)
3,二叉树的层序遍历
之前讲的都是递归的(前序,中序,后序。他们叫深度优先遍历),现在整个不是递归的。
这个层序遍历叫广度优先遍历。不用递归了,用队列(先进先出)。核心:上一层带下一层。
举个例子:
首先构建一个新的队列,一开始放A进队列,然后判断队列是不是空,队列不是空,然后将A拿出来然后将B,C放进去,队列不是空,拿B出来,然后将D,E放进去,队列不是空,拿C出来然后将F,G放进去,队列不是空,将D拿出来,不带。队列不是空,将E拿出来然后再将H放进去,然后将F拿出来,然后将G拿出来,最后将H拿出来,这个时候队列就是空了,然后就结束了。
觉得对自己有帮助的小伙伴可以点个赞哦
|