目录
一、数据结构
数据结构研究的是:数据元素之间的关系----数据结构
数据元素之间常见的逻辑关系有:

物理结构关系---存储到计算机中的结构
计算机本身的存储结构---线性
计算机的线性存储空间中表示不同的逻辑机构
用什么样的数据结构表示对应的逻辑关系
顺序结构:

在内存中:

链式结构:


二、算法介绍
1.数据结构的三要素就是:
逻辑结构 + 物理结构 +算法(不同的数据结构所对应的算法不同)
2.算法特性:输入、输出、有穷性、确定性、可行性
3.设计算法:正确性、健壮性、可读性、时间和空间效率
4.算法的好坏的度量:算法效率:主要看时间复杂度,空间复杂度是次要的
时间复杂度: 空间复杂度:
//O(1)最好 //O(n)最坏 //O(1)(原地插入排序) //O(n)(非原地插入排序)
线性表学习:
顺序表---在c语言中起始就是数组,以顺序结构存储的线性表
链表 ----以链表结构存储的线性表
三、链表

1.定义链表结构体:
创建一个data类型和指针类型变量:
cstypedef int data_t; typedef struct node { data_t data;//数据 struct node *pnext;//下一个数据的地址 }node_t;
2.创建空链表:
头结点不需要装数据,所以直接开在堆上,并判断是否开堆成功,再把指针放在null上即可

cs
//创建头结点
node_t *creat_linnklist(void)//返回结构体类型的指针
{
node_t *head = malloc(sizeof(node_t));//堆上开空间
if(head ==NULL)//创建失败
{
printf("malloc fail");
return NULL;
}
head->pnext =NULL;//创建成功,此时是空链表
return head;
}
3.判断节点是否为空
顾名思义当节点是空节点或者根本没有链表就返回1
cs
//判断节点的指针域是否为空
int is_empty(node_t *phead)
{
if(phead ==NULL)
{
return phead == NULL;
}
return phead->pnext == NULL;
}
4.打印链表:
先判断链表是否为空,不为空遍历链表打印即可
cs
//打印链表
void printf_linklist(node_t *phead)
{
if(is_empty(phead)==1)//检查头节点指针域是否为空
{
printf("empty list!\n");
return ;
}
node_t *p = phead->pnext;
while(p!=NULL)//节点到达空地址时结束,
//注意不是节点的指针域为空的时候结束
{
printf("%d",p->data);
p = p->pnext;
}
putchar('\n');
return ;
}
5.长度计算:
和遍历打印一样,加个计数cnt即可
cs
//长度计算
int lenth_linklist(node_t *phead)
{
int cnt =0;
if(is_empty(phead)==1)//检查头节点指针域是否为空
{
return 0 ;
}
node_t *p = phead->pnext;
while(p!=NULL)//节点到达空地址时结束,
//注意不是节点的指针域为空的时候结束
{
p = p->pnext;
cnt++;
}
putchar('\n');
return cnt;
}
6.头插法:
先创建新的节点,然后把新节点的指针域放在NULL,把头节点的指针域即原来的首节点给到新的节点的指针域,并把新节点地址给到头节点指针域

cs
//头插法
void linklist_insert_head(node_t *phead,data_t data)//返回结构体类型的指针
{
//step1:创建一个新节点并给值
node_t *pnew = malloc(sizeof(node_t));//堆上开空间
if(pnew ==NULL)//创建失败
{
printf("malloc fail");
return ;
}
pnew->pnext = NULL;
pnew->data =data;
//step2:新节点指向首结点
pnew->pnext = phead->pnext;
//step3:头结点指针域指向新节点
phead->pnext = pnew;
}
7.尾插法:
创建一个新节点并把指针域给到NULL,从头节点开始遍历链表,找到尾节点,判断尾节点指针域是否为0,把新节点的地址给到尾节点指针域

cs
//尾插法
void linklist_insert_tail(node_t *phead,data_t data)//返回结构体类型的指针
{
//step1:创建一个新节点并给值
node_t *pnew = malloc(sizeof(node_t));//堆上开空间
if(pnew ==NULL)//创建失败
{
printf("%s :malloc fail",__func__);//宏
return ;
}
pnew->pnext = NULL;
pnew->data =data;
//step2:找到尾节点并判断
node_t *p = phead;
while(p->pnext!=NULL)//节点到达空地址时结束
{
p = p->pnext;
}
//step3:链接
p->pnext = pnew;
}
8.查找:
判断链表是否为空后,从首节点开始遍历,找到key就返回
node_t *linklist_find_key(node_t *phead,data_t key)
{
if(is_empty(phead)==1)//检查头节点指针域是否为空
{
return NULL ;
}
node_t *p = phead->pnext;
while(p!=NULL)//节点到达空地址时结束,
{
if(p->data == key)
{
return p;
}
p = p->pnext;
}
return p;
}
9.修改:
与查找思路相同,把值替换即可:
cs
node_t *linklist_find_updata(node_t *phead,data_t data_old,data_t data_new)
{
if(is_empty(phead)==1)//检查头节点指针域是否为空
{
return NULL ;
}
node_t *p = phead->pnext;
while(p!=NULL)//节点到达空地址时结束,
{
if(p->data == data_old)
{
p->data = data_new;
return p;
}
p = p->pnext;
}
return p;
}
10.尾删法:
判断链表是否为空,然后遍历的条件设置为尾节点前一个,即找到尾节点前一个节点,找到后把尾节点free再把尾节点前一个节点的指针域赋值NULL
注意:从头节点开始,如果从首节点开始若只有一个节点,就会出现段错误,因为p->pnext->pnext不存在

cs
void linklist_delete_tail(node_t *phead)
{
if(is_empty(phead)==1)//检查头节点指针域是否为空
{
return ;
}
node_t *p = phead;//从头节点开始,
//如果从首节点开始若只有一个节点,就会出现段错误
//因为p->pnext->pnext不存在
while((p->pnext)->pnext != NULL)
{
p = p->pnext;
}
free(p->pnext);
p->pnext = NULL ;
}
11.头删法:
判断链表是否为空,定义一个指针变量指向首节点,让头节点指向这个指针变量的指针域即首节点后面一个节点的地址,然后free(首节点)

cs
//头删
void linklist_delete_head(node_t *phead)
{
if(is_empty(phead)==1)//检查头节点指针域是否为空
{
return ;
}
node_t *p = phead->pnext;//指向首节点
phead->pnext = p->pnext;//指向首届点指针域
free(p);
}
12.查找删除:
判断链表是否为空,定义两个指针变量,一个prev表示当前节点的上一个节点,一个curr表示当前节点,然后遍历,并判断当前节点到达给定的值时把当前节点的指针域给到前一个节点的指针域,然后free当前节点即可删除查找的节点,注意不要忘记在循环里面上一个节点与当前节点都要往后移动

cs
//查找删除
void linklist_delete_in(node_t *phead,data_t key)
{
if(is_empty(phead)==1)//检查头节点指针域是否为空
{
return ;
}
node_t *prev = phead;
node_t *curr = phead->pnext;
while(curr!= NULL)
{
if( curr->data == key)
{
prev->pnext = curr->pnext;
free(curr);
return ;
}
prev = prev->pnext;
curr = curr->pnext;
}
return ;
}
13.销毁整个链表:
依旧判断链表是否为空,定义一个指针变量p表示当前节点,从首节点开始遍历,当变量p走到NULL时删除完成(不是p的指针域到达NULL),再定义一个指针变量temp用于记录当前的节点p
将p地址给到temp后,先是p往后移动再是free(temp),
注意1:先后顺序很重要!不然会段错误!一定是temp和p的地址不同时,才free
注意2:free完后要把phead指针指到NULL(预防指针悬空)所以在传参时要传二级指针,否则改变不了phead的地址
注意3:有环的链表调用这个函数会出现无限循环

cs
//删除整个链表
void linklist_destory(node_t**phead)
{
if(*phead ==NULL)
{
return ;
}
node_t *p =(*phead)->pnext;
while(p!=NULL)
{
node_t *temp =p;
p = p->pnext;
free(temp);
}
free(*phead);
*phead =NULL;
}
14.找中间节点:
判断是否为空,定义一个慢指针和快指针,快指针动两步,慢指针动一步,快指针的指针域或者快指针地址到达NULL时,此时慢指针则是中间节点
注意1:循环条件是(指针域!=NULL)和(&&)
注意2:循环条件一定是pf!=NULL再是pf->pnext!=NULL,因为短路特性,如果顺序换一下会断错误。

cs
//找中间节点
node_t* linklist_find_mid(node_t *phead)
{
if(is_empty(phead)==1)
{
return NULL;
}
node_t *pf =phead->pnext;
node_t *ps =phead->pnext;
while((pf !=NULL) &&(pf->pnext !=NULL))
{
ps = ps->pnext;
pf = (pf->pnext)->pnext;
}
return ps;
}
15.找倒数第k个节点:
判断是否为空,定义两个指针都从首节点开始,一个指针先走k步,然后两个指针一起动,当最先走k步的指针到达NULL时,后面那个指针就是倒数第k个节点了。
注意1:从首节点开始,循环里面的判断条件是节点地址到达NULL而不是节点的指针域到达NULL
注意2:key有可能超过链表长度,所以在循环里面判断一下p是否到NULL了,到达就返回NULL

cs
//找倒数第k个节点
node_t *linklist_find_kth_end(node_t *phead,int key)
{
if(is_empty(phead)==1)
{
return NULL;
}
node_t *p1 =phead->pnext;
node_t *p2 =phead->pnext;
for(int i=0;i<key;i++)
{
p1 = p1->pnext;
if (p1 ==NULL)//key有可能比链表还长
return NULL;
}
while(p1 != NULL)
{
p1 = p1->pnext;
p2 = p2->pnext;
}
return p2;
}
16.判断链表是否有环:
判断是否为空,定义一个慢指针和快指针, 与中间节点类似,判断条件也是一样:奇数偶数个节点末尾跳出循环即快指针的指针域或者快指针地址到达NULL时,返回没环。若有环,循环里面判断快慢指针相等返回有环即可
注意:要构造有环的链表只需要使用找到倒数第k个节点,找到倒数第一个把它的指针域给到倒数第任意一个节点的指针域即可

cs
//判断链表是否有环
int linklist_has_cycle(node_t *phead)
{
if(is_empty(phead)==1)
{
return -1;
}
node_t *pf =phead->pnext;
node_t *ps =phead->pnext;
while((pf !=NULL) &&(pf->pnext !=NULL))
{
ps = ps->pnext;
pf = (pf->pnext)->pnext;
if(ps == pf)
return 1;
}
return 0;
}
17.链表的逆序:
判断是否为空,以及首尾节点是否是同一个。定义两个指针表示头节点和首节点,将头节点与首节点断开,即头节点指针域置为空。要用到三个指针变量:
注意:
node_t *ptemp = next; //定义一个用于插入的temp节点
next = next->pnext;//往下走必须是在头插之前
//如果我先头插再往下走会出现无法往下走
//因为temp的指针域变成了NULL,与后面的节点断开
/再用头插法
ptemp->pnext = p->pnext;
p->pnext = ptemp;
cs
//链表逆序
void linklist_reverse(node_t *phead)
{
if(is_empty(phead)==1|| phead->pnext->pnext ==NULL)//判断链表是否为空,
//首节点和尾节点是否是同一个节点
{
return ;
}
node_t *p =phead;
node_t *next =phead->pnext;
//头结点的指针域置为null,即与首节点断开
p->pnext = NULL;
while(next !=NULL)
{
node_t *ptemp = next;//定义一个用于插入的temp节点
next = next->pnext;//往下走必须是在头插之前
//如果我先头插再往下走会出现无法往下走,因为temp的指针域变成了NULL,与后面的节点断开了
//头插法
ptemp->pnext = p->pnext;
p->pnext = ptemp;
}
return ;
}
