文章仅个人学习数据结构的实现记录,对理论性文字很少,仅仅粘贴实现的c代码以及部分测试,其中测试结果无说明
顺序表
malloc函数分配的空间是在堆区,需要程序员自行回收(free)
- 初始化顺序表的实现——静态结构
这里要注意指针的使用
/*
This is a define of sequence
*/
#include<stdio.h>
#define MaxSize 10 // the max size
// Describe the sequence
typedef struct{
int data[MaxSize]; // this are elements
int length;// the real size
}SqList;
// Init
void InitList(SqList *L){
for(int i=0;i<MaxSize;i++){
L->data[i]=0;
}
// set the real size
L->length=0;
}
int main(){
SqList s;
// &s:it is an address
InitList(&s);
s.data[0]=1;
s.length++;
for(int i=0;i<s.length;i++){
printf("%d\n",s.data[i]);
}
}
- 插入——健壮性
// Insert
bool ListInsert(SqList *L,int i,int e){
// this index is not legal;tips:i means 位序, not the index
if((i-1)<0||(i-1)>L->length)
return false;
// there is no position you can insert into
if(L->length>=MaxSize)
return false;
// TODO if e is not legal
for(int j=L->length-1;j>=(i-1);j--){
L->data[j+1]=L->data[j];
}
L->data[i-1]=e;
L->length++;
return true;
}
- 删除
// Delete
bool ListDelete(SqList *L,int i,int *e){
// this index is not legal;tips:i means 位序, not the index
if((i-1)<0||(i-1)>L->length)
return false;
*e=L->data[i-1];
// move the element to the next position(while index>i)
for(int j=i-1;j<L->length;j++){
L->data[j]=L->data[j+1];
}
L->length--;
return true;
}
- 按照下标查找
// find by index
int GetElem(SqList L,int i){
if((i-1)<0||(i-1)>L.length)
return -1;
return L.data[i-1];
}
- 按照元素查找
// find by element
int LocateElem(SqList L,int e){
for(int i=0;i<L.length;i++){
if(L.data[i]==e){
return i+1;
}
}
return -1;
}
单链表
- 单链表的定义(是否带头结点)
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
// linklist
// define linklist
typedef struct LNote{
int data;
struct LNode *next;
}LNode,*LinkList;//重命名数据类型为LNode,并且重命名其指针为LinkList,当说明的是LNode* 的时候,表明这是一个节点,当写的是LinkList的时候,说明这是一个单链表
// init
bool InitList_withoutHead(LinkList *L){
// NULL:avoid dirty data
(*L)=NULL;
return true;
}
bool InitList_withHead(LinkList *L){
*L=(LNode *)malloc(sizeof(LNode));
if(*L==NULL)
return false;
(*L)->next=NULL;
return true;
}
// judge
bool Empty(LinkList L){
if(L==NULL)
return true;
else
return false;
}
- 单链表的插入——按位序插入(带头结点)
// Insert
bool Listinsert_withHead(LinkList *L,int i,int e){
// i means 位序
if(i<1)
return false;
LNode *p;//指针p指向当前扫描到的节点
int j=0;// 当前p指向的是第几个节点,这里指向的是头结点,认为是0节点
p=(*L);
// p->data=1111;
// printf("%d\n",p->data);
while(p!=NULL&&j<i-1){
p=p->next;
j++;
}
// i is not legal
if(p==NULL)
return false;
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;// 这两句次序不可以改变
p->next=s;
return true;
}
// test
void test1(){
LinkList L;
// L做头结点
InitList_withHead(&L);
Listinsert_withHead(&L,1,11);
Listinsert_withHead(&L,2,22);
Listinsert_withHead(&L,3,33);
LNode *p=L->next;
while(p!=NULL){
printf("%d\n",p->data);
p=p->next;
}
}
-
单链表的插入——按位序插入(不带头结点)
这个时候,当n=1的时候,需要特别使用一段代码来实现,特别麻烦
bool Listinsert_withoutHead(LinkList *L,int i,int e){
if(i<1)
return false;
if(i==1){
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=*L;
// 需要更改头指针,使得L(头指针)直接就是s
*L=s;
return true;
}
LNode *p;
int j=1;//没有头结点,也就是0节点,所以当前p是从第一个节点开始的
p=*L;
while(p!=NULL&&j<i-1){
p=p->next;
j++;
}
if(p==NULL)
return false;
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
void test2(){
LinkList L;
InitList_withoutHead(&L);
Listinsert_withoutHead(&L,1,1);
Listinsert_withoutHead(&L,1,2);
Listinsert_withoutHead(&L,1,3);
Listinsert_withoutHead(&L,1,4);
LNode *p=L;
while(p!=NULL){
printf("%d\n",p->data);
p=p->next;
}
}
因为不带头结点的单链表插入删除非常麻烦,所以后续的代码没有特别说明都是带头结点的。
- 指定节点后插操作
时间复杂度为O(1)
// Insert element after designated node
bool InsertNextNode(LNode *p,int e){
if(p==NULL)
return false;
LNode *s=(LNode *)malloc(sizeof(LNode));
// 内存分配失败
if(s==NULL)
return false;
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
// test
void test1(){
LinkList L;
// L做头结点
InitList_withHead(&L);
Listinsert_withHead(&L,1,11);
Listinsert_withHead(&L,2,22);
Listinsert_withHead(&L,3,33);
// 不可以出现两个p->next->next,不然要p=p->next干啥
LNode *p=L->next;
p=p->next;
InsertNextNode(p,123);
while(p!=NULL){
printf("%d\n",p->data);
p=p->next;
}
}
对于元素的指定位置i的插入操作,只要找到第i-1个节点,就可以使用后插操作实现对指定位置i的插入操作。那么指定位置插入的代码会变为以下。
bool Listinsert_withHead(LinkList *L,int i,int e){
// i means 位序
if(i<1)
return false;
LNode *p;//指针p指向当前扫描到的节点
int j=0;// 当前p指向的是第几个节点,这里指向的是头结点,认为是0节点
p=(*L);
// p->data=1111;
// printf("%d\n",p->data);
while(p!=NULL&&j<i-1){
p=p->next;
j++;
}
return InsertNextNode((LNode *)p,e);
// i is not legal
// if(p==NULL)
// return false;
// LNode *s=(LNode *)malloc(sizeof(LNode));
// s->data=e;
// s->next=p->next;// 这两句次序不可以改变
// p->next=s;
// return true;
}
- 指定结点前插
节点不能跑路,但是结点的数据可以变化,当需要指定位置前插的时候,可以考虑申请一个后继节点s放在p节点后,修改s的数据为要p的数据2,修改p的数据为要插入的数据,这样实现的时间复杂度为O(1)
bool InsertPriorNode(LNode *p,int e){
// 时间复杂度为O(1)
if(p==NULL)
return false;
LNode *s=(LNode *)malloc(sizeof(LNode));
// 内存分配失败
if(s==NULL)
return false;
s->data=p->data;
s->next=p->next;
p->next=s;
p->data=e;
return true;
}
// test
void test1(){
LinkList L;
// L做头结点
InitList_withHead(&L);
Listinsert_withHead(&L,1,11);
Listinsert_withHead(&L,2,22);
Listinsert_withHead(&L,3,33);
LNode *p=L->next;
p=p->next;
InsertPriorNode(p,123);
while(p!=NULL){
printf("%d\n",p->data);
p=p->next;
}
}
- 头插法建立单链表——链表的逆置
头插法建立单链表的时候,读入的数据与生成的链表的元素的顺序是相反的,当数据规模是n的时候,时间复杂度为O(n)
// 头插法建立单链表
void List_HeadInsert(LinkList *L){
LNode *s;
int e;
scanf("%d",&e);
while(e!=999){
s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=(*L)->next;
(*L)->next=s;
scanf("%d",&e);
}
// return L;
}
void test2(){
LinkList L;
InitList_withHead(&L);
List_HeadInsert(&L);
LNode *p=L->next;
while(p!=NULL){
printf("%d->",p->data);
p=p->next;
}
}
- 尾插法建立单链表
时间复杂度和头插法一样,它生成链表的顺序和我们输入数据的顺序是一样的,尾插法需要动态维护一个尾指针,值得注意的是,如果没有把尾指针指向NULL,程序可能会出现不可预知的后果
// 尾插法建立单链表
void List_TailInsert(LinkList *L){
int e;
LNode *s,*r=*L;
scanf("%d",&e);
while(e!=999){
s=(LNode *)malloc(sizeof(LNode));
s->data=e;
r->next=s;
r=s;
scanf("%d",&e);
}
// 如果这里不添加NULL,则r的后面就会随意指向一个地址,将出现大问题
r->next=NULL;
}
// print LinkList
void printfLinkList(LinkList L){
LNode *p=L->next;
while(p!=NULL){
printf("%d->",p->data);
p=p->next;
}
}
void test2(){
LinkList L;
InitList_withHead(&L);
List_TailInsert(&L);
printfLinkList(L);
}
- 按照位序删除结点
首先先检验位置的合法性,查找节点的第i-1个结点,最后进行删除操作。由于查找的事件复杂度为O(n),所以整体上时间复杂度为O(n)
if(p->next==NULL)//i-1后面没结点,不需要删除
return false;
// 按位序删除-带头结点
bool ListDelete(LinkList *L,int i,int *e){
if(i<1)
return false;
LNode *p=*L;// p指向当前扫描到的结点
int j=0;//指向p的第几个节点
while(p!=NULL&&j<i-1){//循环找到第i个结点
p=p->next;
j++;
}
if(p==NULL)//i的值不合法
return false;
if(p->next==NULL)//i-1后面没结点,不需要删除
return false;
LNode *q=p->next;
p->next=q->next;
*e=q->data;
free(q);
return true;
}
- 指定结点的删除
如果给定一个头结点,可以找到指定节点的前驱节点,但是这样的事件复杂度为O(n),可以换一种思路,首先删除给定结点的后继节点,然后把后继节点的数据修改到当前指定节点的数据,这样的时间复杂度为O(1)
//有bug的代码,当删除的是最后一个节点的时候,执行到p->data=q->data;的时候会发生空指针错误
bool ListDelete_piont(LNode *p){
if(p==NULL)
return false;
LNode *q=p->next;
p->data=q->data;
p->next=q->next;
free(q);
return true;
}
但是如果删除的结点p是最后一个结点,不能直接free§,这样前驱节点指向的下一个节点会随机,会造成不可预知的后果。如果是这种情况下的删除,并且当前给出了链表的头指针,可以考虑先找到单链表的前驱节点,再删除结点。
- 按位查找
事件复杂度为O(n),这里我们认为,头结点为0号节点
LNode *GetElem(LinkList L,int i){
if(i<0)
return NULL;
LNode *p=L;
int j=0;
while(p!=NULL&&j<i){
p=p->next;
j++;
}
return p;
}
- 按值查找
时间复杂度为O(n)
LNode *GetElem_element(LinkList L,int e){
LNode *p=L->next;
while(p!=NULL&&p->data!=e)
p=p->next;
return p;
}
双链表
- 初始化双链表
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
typedef struct DNode{
int data;
struct DNode *prior,*next;
}DNode,*DLinklist;
// init Double Linklist
bool InitDLinklist(DLinklist *L){
// 分配一个头结点
*L=(DNode *)malloc(sizeof(DNode));
if(*L==NULL)//内存分配失败
return false;
(*L)->prior=NULL;
(*L)->next=NULL;
return true;
}
// judge double linklist is empty or not
bool Empty(DLinklist L){
if(L->next==NULL)
return true;
return false;
}
- 在p结点后面插入一个双链表——后插操作
// insert a new node after designed node-p
//如果p结点是最后一个结点,那么在执行p->next->prior=s;会爆空指针错误
bool InsertNextDNode(DNode *p,DNode *s){
s->next=p->next;
p->next->prior=s;
s->prior=p;
p->next=s;
}
更具有健壮性的代码
// insert a new node after designed node_p
bool InsertNextDNode(DNode *p,DNode *s){
if(p==NULL||s==NULL)//非法参数
return false;
s->next=p->next;
if(p->next!=NULL)//没有后继结点的情况
(p->next)->prior=s;
s->prior=p;
p->next=s;
return true;
}
void printfDLinklist(DLinklist L){
DNode *p=L->next;
while(p!=NULL){
printf("%d->",p->data);
p=p->next;
}
}
void test1(){
DLinklist L;
InitDLinklist(&L);
DNode *s=(DNode *)malloc(sizeof(DNode));
DNode *p=(DNode *)malloc(sizeof(DNode));
p=L;
s->data=64;
InsertNextDNode(p,s);
printfDLinklist(L);
}
void main(){
test1();
}
- 删除结点
// delete node
bool DeleteNextLinklist(DNode *p,int *e){
if(p==NULL||p->next==NULL)
return false;
DNode *q=p->next;
*e=q->data;
if(q->next!=NULL)
q->next->prior=p;
p->next=q->next;
free(q);
return true;
}
void test1(){
DLinklist L;
InitDLinklist(&L);
DNode *s=(DNode *)malloc(sizeof(DNode));
DNode *q=(DNode *)malloc(sizeof(DNode));
DNode *p=(DNode *)malloc(sizeof(DNode));
p=L;
s->data=64;
s->next=NULL;
InsertNextDNode(p,s);
q->data=63;
q->next=NULL;
InsertNextDNode(p,q);
printfDLinklist(L);
int e=0;
DeleteNextLinklist(p,&e);
printf("\n");
printfDLinklist(L);
}
- 销毁双链表
void DestoryList(DLinklist *L){
int e=0;
// 循环删除L的后继结点
while((*L)->next!=NULL){
DeleteNextLinklist(*L,&e);
}
free(*L);
*L=NULL;
}
循环链表
在定义循环链表的时候,如果使用的是头指针,在查询最后一个节点的时候时间复杂度为O(n),但是如果指针L指向表尾元素,那么从尾部找到头部和直接找尾部的时间复杂度都是O(1),这对于经常要使用头尾结点的场景很友好。
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
typedef struct LNode{
int data;
struct LNode *next;
}LNode,*LinkList;
// init
bool InitList(LinkList *L){
*L=(LNode *)malloc(sizeof(LNode));
if(*L==NULL)
return false;
(*L)->next=*L;
return true;
}
// judge
bool Empty(LinkList L){
if(L->next==L)
return true;
return false;
}
// judge whether the p node is a tail pointer
bool isTail(LinkList L,LNode *p){
if(p->next==L)
return true;
return false;
}
void test1(){
LinkList L;
InitList(&L);
if(Empty(L))
printf("yes!");
if(isTail(L,L))
printf("is tail!");
}
void main(){
test1();
}
循环双链表
- init
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
typedef struct CDNode{
int data;
struct CDNode *prior,*next;
}CDNode,*CDLinklist;
// init
bool InitCDLinklist(CDLinklist *L){
*L=(CDNode *)malloc(sizeof(CDNode));
if(*L==NULL)
return false;
(*L)->prior=*L;
(*L)->next=*L;
return true;
}
// judge whether the cdlinklist is empty
bool Empty(CDLinklist L){
if(L->next==L)
return true;
return false;
}
// judge whether the p node is tail pointer
bool isTail(CDLinklist L,CDNode *p){
if(p->next==L)
return true;
return false;
}
void test1(){
CDLinklist L;
InitCDLinklist(&L);
if(isTail(L,L))
printf("init completely!");
}
void main(){
test1();
}
- insert
特别要注意的是,这里的输出的判断是p的下一个节点是否是L,因为这个是循环双链表
// insert a new node s after p node
bool InsertNextCDNode(CDNode *p,CDNode *s){
s->next=p->next;
p->next->prior=s;
s->prior=p;
p->next=s;
}
void printfCDLinklist(CDLinklist L){
CDNode *p=L->next;
while(p!=L){
printf("%d->",p->data);
p=p->next;
}
}
void test1(){
CDLinklist L;
InitCDLinklist(&L);
CDNode *s=(CDNode *)malloc(sizeof(CDNode));
s->data=111;
s->next=NULL;
s->prior=NULL;
CDNode *x=(CDNode *)malloc(sizeof(CDNode));
x->data=222;
x->next=NULL;
x->prior=NULL;
InsertNextCDNode(L,s);
InsertNextCDNode(s,x);
printfCDLinklist(L);
}
void main(){
test1();
}
静态链表
静态链表首先要分配一整片连续的内存空间,各个结点集中安置。静态链表和单链表类似,包含数据元素和指针元素(游标),但是这里的指针元素指向的是下一个节点的地址(数组下标)。并且0结点充当“头结点”。静态链表对于一些不支持指针的高级语言来讲设计是很巧妙的。
- 定义一个结构体
#include<stdlib.h>
#include<stdbool.h>
#include<stdio.h>
#define MAXSIZE 10//这里是不允许有分号结束符的
typedef struct{
int data;
int next;//下一个元素的数组下标
}SLinklist;
void test(){
SLinklist s[MAXSIZE];
printf("%d\n",sizeof(s));
}
void main(){
test();
}
- init
// init
bool initSLinklist(SLinklist s[MAXSIZE]){
// 值得注意的是,在c的标准中是没有定义数组的长度进行定义的,但是在定义一个数组的时候,要求数组中括号中的数必须是常量和或者常量表达式,所以这个常量是可以作为长度传入的,但是这一操作却是在java中被允许的
if(MAXSIZE==0)
return false;
s[0].next=-1;
return true;
}
void test(){
SLinklist s[MAXSIZE];
if(initSLinklist(s))
printf("yes!\n");
printf("%d\n",sizeof(s));
}
void main(){
test();
}