链表
1.链表就是一种数据结构,可以从上一个数据找到下一个数组的位置。从而进行访问。
一般标准的符合上述描述的就是单链表。‘
在理解具体结结构的基础上,就可以着手链表的创建、销毁以及增删查改.
具体在于头插,尾插,头删,尾删,某个位置插入数据,查找数组,删除某位置数据
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLdata;
typedef struct SLCA {
struct SLCA* p;
SLdata a;
}CA;//取名叫CA,是因为查单词出现偏差,chain list,CA也就是链表的结构体
//里面包含了一个指针,指向下一个数据
void SL_firinsert(CA** pca,SLdata data);
void SL_endinsert(CA** pca,SLdata data);
void SLprint(CA* pca);
CA* Crea_node(SLdata X);
void SL_deletefir(CA** pca);
void SL_deletefend(CA** pca);
CA* SLdfind(CA* phead,SLdata data);
void SLindata(CA** phead,CA* pos,SLdata x);
void SLindata2(CA** phead,CA* pos,SLdata x);
void SLdele_now(CA** phead,CA* pos);
void SLdele_aft(CA** phead,CA* pos);
void SLprint(CA* pca)//打印链表,可以传空
{
CA* cur = pca;
while(cur)
{
printf("%d ->",cur->a);
cur = cur->p;
}
printf("NULL\n");
return ;
}
CA* Crea_node(SLdata X)//创建一个链表节点
{
CA* newnode = (CA*)malloc(sizeof(CA));
if(!newnode)
{
perror("malloc fail:");
return NULL;
}
newnode->a = X;
newnode->p = NULL;
return newnode;
}
void SL_firinsert(CA** pca,SLdata data)//头插,这里传二级指针,是为了确实修改节点,
//如果直接传以及指针,是不是就无法修改你改变头节点之后的值的还回去
{
assert(pca);
CA* cur = *pca;
CA* temp = Crea_node(data);
temp ->a = data;
temp->p = cur;
*pca = temp;
return;
}
void SL_endinsert(CA** pca,SLdata data)//尾插
//这里一样,如果是一开始传的空头,那么就需要二级指针,
{
assert(pca);
CA* temp = Crea_node(data);
temp->a = data;
temp->p=NULL;
CA* p = *pca;
if(p)
{
while(p->p)
{
p = p->p;
}
p->p = temp;
}
else
{
*pca = temp;
}
return ;
}
void SL_deletefir(CA** pca)
{
assert(pca);
if(*pca)//不为空,去删除
{
CA* cur = *pca;
*pca = cur->p;
free(cur);//free the place malloc;
}
else{
perror("plist is NULL\n");
return ;//已经为空
}
}
void SL_deletefend(CA** pca)
{
assert(pca);
if(*pca)//不为空,去删除
{
if((*pca)->p)
{
CA* cur = *pca;
while(cur->p->p)
{
cur = cur->p;
}
free(cur->p);//free the place malloc;
cur->p = NULL;
}
else
{
free(*pca);
*pca = NULL;
}
}
else{
perror("plist is NULL\n");
return ;//已经为空
}
}
CA* SLdfind(CA* phead,SLdata data)
{
CA* cur = phead;
while(cur)
{
if(cur->a== data)
{
return cur;
}
cur = cur->p;
}
printf("NO this DATA!\n");
return NULL;
}
//某节点前插入
void SLindata(CA** phead,CA* pos,SLdata x)
{
assert(phead);
CA* cur = *phead;
if(!(*phead))
{
*phead = Crea_node(x);
return ;
}
if(!pos)
{
perror("pos is NULL\n");
return;
}
if(*phead == pos)//solve teh probem if here only one value or it the head of the list insert
{
SL_firinsert(phead,x);
return ;
}
while(cur && cur->p != pos)//if cur is NULL ,exit
{
cur = cur->p;
}
if(cur)
{
CA*newnode = Crea_node(x);
newnode->p = pos;
cur->p = newnode;
}
else
{
printf("NO pos int the list\n");
}
}
void SLindata2(CA** phead,CA* pos,SLdata x)
{
assert(phead);
CA* cur = *phead;
if(!(*phead))
{
*phead = Crea_node(x);
return ;
}
if(!pos)
{
perror("pos is NULL\n");
return;
}
while(cur && cur!=pos)
{
cur = cur->p;
}
if(cur)
{
CA*newnode = Crea_node(x);
cur->p = newnode;
}
else
{
printf("NO pos int the list\n");
}
}
void SLdele_aft(CA** phead,CA* pos)
{
assert(phead);
CA* cur = *phead;
if(!(*phead))
{
printf("thee head is NULL!\n");
return ;
}
while(cur && cur!=pos)
{
cur = cur->p;
}
if(!cur->p)
{
printf("the pos after the target is NULL.\n");
}
else
{
CA* t = pos->p;
cur->p = cur->p->p;
free(t);
}
}
void SLdele_now(CA** phead,CA* pos)
{
assert(phead);
if(!pos)
{
perror("pos is NULL\n");
return ;
}
CA* cur = *phead;
if(!(*phead))
{
printf("thee head is NULL!\n");
return ;
}
if(cur==pos)
{
*phead = cur->p;
free(pos);
}
else
{
while(cur->p &&cur->p!=pos)
{
cur = cur->p;
}
if(cur->p)
{
cur->p = cur->p->p;
free(pos);
}
}
}
详细需要注意的我标准在代码里面了。可以自己动手去实现下。牢记物理结构,结合C语言特征去尝试的调试代码即可。
链表特点:根据代码和物理结构,我们就知道,链表对数据头部的改动是非常方便的,但是对于随机访问和查找数据就不方便。
同时也不方便访问尾部数据。
这里其实有一种解决方式,就是双向链表。
有种很好用的就是带头双向链表。
如下图:
typedef int SLdata;
struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
SLdata val;
};
typedef struct ListNode LIST;
void init(LIST ** head);
void pushback(struct ListNode* head,SLdata x);
void pushfront(struct ListNode* head ,SLdata x);
void SLprint(struct ListNode* head);
void popback(struct ListNode* head);
void popfront(struct ListNode* head);
struct ListNode*SLfind(struct ListNode* head,SLdata x);
void intsert_befpos(struct ListNode* pos,SLdata x);
void intsert_aftpos(struct ListNode* pos,SLdata x);
struct ListNode* creat_node(SLdata x)
{
struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode));
node->prev=NULL;
node->next=NULL;
node->val=x;
return node;
}
void init(LIST ** head)
{
*head = (struct ListNode*)malloc(sizeof(struct ListNode));
(*head)->next =(*head);
(*head)->prev = (*head);
(*head)->val = -1;
}
void pushback(struct ListNode* head,SLdata x)//尾插
{
assert(head);
LIST* cur = head;
LIST* newnode = creat_node(x);
cur = cur->prev;
cur->next = newnode;
newnode->next = head;
newnode->prev = cur;
head->prev = newnode;
}
void pushfront(struct ListNode* head,SLdata x)
{
assert(head);
LIST* cur = head->next;
LIST* newnode = creat_node(x);
cur->prev = newnode;
newnode->next = cur;
newnode->prev = head;
head->next = newnode;
}
void SLprint(struct ListNode* head)
{
assert(head);
assert(head->next!=head);
printf("guard-> ");
struct ListNode* cur = head->prev;
while (cur!=head)
{
printf("%d->",cur->val);
cur = cur->prev;
}
printf("\n");
}
void popback(struct ListNode* head)
{
assert(head);
assert(head->next!=head);
struct ListNode *cur = head->prev;
struct ListNode * fir = cur->prev;
fir->next = head;
head->prev = fir;
free(cur);
}
void popfront(struct ListNode* head)
{
assert(head);
assert(head->next!=head);
struct ListNode *cur = head->next;
struct ListNode * sec = cur->next;
head->next = sec;
sec->prev = head;
free(cur);
}
struct ListNode* SLfind(struct ListNode* head,SLdata x)
{
assert(head);
assert(head->next!=head);
struct ListNode *cur = head->next;
while(head != cur)
{
if(cur->val==x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
void intsert_befpos(struct ListNode* pos,SLdata x)
{
if(!pos)
{
perror("the pos is NULL\n");
return ;
}
struct ListNode* pre = pos->prev;
LIST* newnode = creat_node(x);
pre->next = newnode;
newnode->next = pos;
newnode->prev = pre;
pos->prev = newnode;
}
void intsert_aftpos(struct ListNode* pos,SLdata x)
{
if(!pos)
{
perror("the pos is NULL\n");
return ;
}
struct ListNode* sec = pos->next;
LIST* newnode = creat_node(x);
sec->prev = newnode;
newnode->next = sec;
newnode->prev = pos;
pos->next = newnode;
}
这种链表使用起来就方便许多,在查找数据和增删查改都方便不少。
顺序表
顺序表其实就是我们常说的数组来实现的,只是一般数据都是结构体类型。
特点:可以通过下标快速访问,对尾部数据操作时比较方便,还可以进行排序等操作,但是挪动头部数据就需要较大的时间复杂度,并且会需要相对较大的内存。
顺序表也是增删查改,几个操作,可以类似
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
enum SLelments{
NAME = 20,
AGE = 10,
GRADE =6
};
//定义一个学生结构体
struct S{
char name[NAME];
int age;
int* grade;//去动态内存开辟
};
typedef int sldata;//这里暂时将int类型定义位本次顺序表的元素类型。
//测试时可以把上面的学生结构体进行替换即可。
struct seplist{
sldata* a;//元素类型
int sz;//这个数据表有效数据个数
int capacity;//这个顺序表容量
};
typedef struct seplist SEL;
void cr_SL(SEL* p);//初始化创建一个顺序表 ,结构体传地址
void des_SL(SEL* p);
void dul_SL(SEL* p);
void c_data(sldata* t,const sldata* tem);
void first_add(SEL*p,const sldata*);
void end_add(SEL*p,const sldata*);//尾部加一个元素
void firdelete(SEL*p);
void enddelete(SEL*p);
void SLprint(SEL*p);
void element_intsert(SEL*p,int pos,const sldata* tem);//防止未来数据过大,传入地址
void element_finre(SEL*p,int pos);
int finselement(SEL*p,const sldata* tem);
void element_modify(SEL*p,int pos,sldata* tem);
void cr_SL(SEL* p)//初始化创建一个顺序表 ,结构体传地址
{
p->a = (sldata*)malloc(sizeof(sldata)*4);//初始化给四个元素
p->sz = 0;//有效数组任然是0;
p->capacity = 4;
return;
}
void des_SL(SEL*p){
free(p->a);//记得置为空指针
p->sz = 0;
p->capacity = 4;
return;
}
void dul_SL(SEL* p){
p->a = (sldata*)realloc(p->a,p->capacity*2*sizeof(sldata));//二倍扩容
p->capacity *=2;
return;
}
//c_data是对数据的赋值,取决于你的数据类型在这里修改赋值 ,
void c_data(sldata* t,const sldata* tem){
*t = (*tem)%11;
return;
}//暂时取整形,每个值赋值10以内 ,后续可在此修改,因为成员类型后续也会改变。也可以修改成函数指针的形式,让使用者自主
void first_add(SEL*p,const sldata* tem){
if(p->sz+2 > p->capacity)//考虑扩容
{
dul_SL(p);
}
memmove(p->a +1,p->a,sizeof(sldata)*(p->sz));
c_data(p->a,tem);//这里传过去就是首元素
p->sz +=1;
return ;
}
void end_add(SEL*p,const sldata* tem)//尾部加一个元素
{
if(p->sz+2 > p->capacity)//考虑扩容
{
dul_SL(p);
}
c_data(&(p->a[p->sz]),tem);
p->sz +=1;
return ;
}
void firdelete(SEL*p)
{
assert(p->sz>0);//注意检查元素个数
memmove(p->a,p->a+1,sizeof(sldata)*(p->sz-1));//首部删除,记得放置成空指针
p->sz-=1;
p->a[p->sz]=0;
return ;
}
void enddelete(SEL*p)
{
assert(p->sz>0);
p->sz-=1;
p->a[p->sz]=0;
return ;
}
void SLprint(SEL*p){
int i=0;
for(;i<p->sz;i++)
{
printf(" %d ",p->a[i]);//暂时打印整形后续换数据类型可以改变,也可以用函数去打印,,或者借助回调函数
}
printf("\n");
return ;
}
void element_intsert(SEL*p,int pos,const sldata* tem)
{
pos -=1;//输入下标一般是1以上的自然数
if(pos == 0)
{
first_add(p,tem);
}
else if(pos<=p->sz &&pos>=0 )
{
if(p->sz+2 > p->capacity)//考虑扩容
{
dul_SL(p);
}
memmove(p->a+pos+1,p->a+pos,(p->sz-pos)*sizeof(sldata));
c_data(&(p->a[pos]),tem);
p->sz +=1;
}
else
{
assert(0);
}
return ;
}
void element_finre(SEL*p,int pos)//给出下标删除
{
pos--;
if(!(p->sz>0))
{
return ;
}
memmove(p->a+pos,p->a+pos+1,(p->sz-pos-1)*sizeof(sldata));
p->sz -=1;
return ;
}
int finselement(SEL*p,const sldata* tem)
{
int i=0;
for(;i<p->sz;i++)
{
if(p->a[i]==*tem)
{
return i;
}
}
return -1;
}
void element_modify(SEL*p,int pos,sldata* tem)
{
c_data(&(p->a[pos-1]),tem);
}
上述代码可以用作参考。
总结
顺序表和链表算是各有优势,在不同的场景具备自己的优劣。熟悉物理结构和实现逻辑更加重要。在之后的队列和栈都是有帮助的。