线性表
线性表是n个具有相同特性的数据元素的有限序列。
常见的线性表:顺序表、链表、栈、队列
线性表在逻辑上是线性结构,连续的一条直线。但是在物理存储结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
顺序表
顺序表是线性表的一种,使用一串物理地址连续的存储单元来存储数据元素。
缺点:
(1)当数据量大时,插入和删除操作麻烦,需要移动大量大元素;
(2)采用数组实现的连续存储,需要先预估数组的大小,是固定的。
优点:
(1)因为有下标,查找速度快;
(2)可以实现随机访问。
静态顺序表
#include <stdio.h>
#include <stdlib.h>
#define N 100
typedef struct
{
int data[N];
int last;
}seqList_t;
//1. 创建一个空的顺序表 createEmpty()
seqList_t* createEmpty()
{
seqList_t* p = (seqList_t*)malloc(sizeof(seqList_t));
if(p == NULL)
{
printf("create failed!!!\n");
return NULL;
}
p->last = 0;
return p;
}
//2. 判断一个顺序表是否为空 空的话返回1 不空返回 0 isEmpty()
int isEmpty(seqList_t* p)
{
return p->last == 0;
}
//3. 判断一个顺序表是否为满 满的话返回1 不满返回 0 isFull()
int isFull(seqList_t* p)
{
return p->last == N;
}
//4. 遍历顺序表中所有有效元素 showSeqlist()
void showSeqlist(seqList_t* p)
{
int i;
for(i = 0;i < p->last;i++)
{
printf("%d ",p->data[i]);
}
printf("\n");
}
//5. 在指定位置插入数据 insertInto(),成功返回1,否则返回0
int insertInto(seqList_t* p,int post,int value)
{
int i;
if(post <= 0 || post > p->last+1 || isFull(p))
{
printf("insert failed\n");
return 0;
}
for(i = p->last-1;i >= post-1;i--)
{
p->data[i+1] = p->data[i];
}
p->data[post-1] = value;
p->last++;
return 1;
}
//6. 删除指定位置上数据 deleteFrom()
int deleteFrom(seqList_t* p,int post)
{
int i;
if(post <= 0 || post > p->last ||isEmpty(p))
{
printf("delete failed\n");
return 0;
}
for(i = post-1;i <= p->last-1;i++)
{
p->data[i] = p->data[i+1];
}
p->last--;
return 1;
}
//7. 求顺序表的长度 getLength()
int getLength(seqList_t* p)
{
return p->last;
}
//8. 查找某个元素出现在顺序表中的位置 findData() 找不到返回0
int findData(seqList_t* p,int value)
{
int i;
for(i = 0;i <= p->last-1;i++)
{
if(p->data[i] == value)
{
return i;
}
}
return -1;
}
//9. 清空顺序表clearSeqlist()
void clearSeqlist(seqList_t* p)
{
p->last = 0;
}
int main()
{
seqList_t* p = createEmpty();
if(NULL == p)
return -1;
insertInto(p,1,33);
insertInto(p,2,44);
insertInto(p,3,55);
insertInto(p,2,1000);
showSeqlist(p);
deleteFrom(p,1);
showSeqlist(p);
int x = 1000;
int index = findData(p,x);
index==-1?printf("未找到%d\n",x):printf("%d index is %d\n",x,index+1);
free(p);
return 0;
}
链表
线性表的链式存储称为链表。
缺点:
(1)查找速度慢;
(2)不可以实现随机访问。
优点:
(1)插入和删除操作很方便,因为不需要移动大量大元素;
(2)不需要先预估存储大小,没有满的状态。
单向链表(带头结点)
#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
int data; //数据域
struct node* next; //指针域,指向下一个结点的首地址
}linknode_t;
/*创建有头结点空的链表*/
linknode_t* creatEmpty()
{
linknode_t *phead=(linknode_t*)malloc(sizeof(linknode_t));
if(phead==NULL)
{
printf("malloc failed\n");
return NULL;
}
phead->next=NULL; //将指针域置空
//数据域不用赋值 因为头结点数据无效
return phead; //将链表在堆区的首地址返回
}
/*遍历有头单向链表*/
void show(linknode_t *p)
{
while(p->next!=NULL)
{
p=p->next;//跨过头结点
printf("%d ",p->data);
}
printf("\n");
}
/*求链表的长度(不包含头结点)*/
int getLength(linknode_t *p)
{
int length=0;
while(p->next!=NULL)
{
p=p->next;
length++;
}
return length;
}
/*在链表的指定位置插入数据*/
int insertInto(linknode_t *p,int post,int x)
{
if(post<=0||post>getLength(p)+1)
{
printf("insert failed\n");
return -1;
}
//插入位置合理,创建一个新的结点存储数据
linknode_t *pnew=(linknode_t*)malloc(sizeof(linknode_t));
if(pnew==NULL)
{
printf("pnew malloc failed\n");
return -1;
}
pnew->data=x;
pnew->next=NULL;
//将头指针移动到插入位置的前一个位置
int i=0;
for(i=0;i<post-1;i++)
{
p=p->next;
}
//将新结点连接到链表上(先连后面,再连前面)
pnew->next=p->next;
p->next=pnew;
return 0;
}
/*判断链表是否为空*/
int isEmpty(linknode_t *p)
{
return p->next==NULL;
}
/*删除指定位置上的数据*/
int deleteFrom(linknode_t *p,int post)
{
if(post<=0||isEmpty(p)||post>getLength(p))
{
printf("delete filed\n");
return -1;
}
int i;
for(i=0;i<post-1;i++)
{
p=p->next;
}
linknode_t *pdel=p->next; //定义一个指针指向被删除节点
p->next=pdel->next; //跨过删除节点指向下一个节点
free(pdel); //释放删除节点在堆区空间
pdel=NULL; //避免野指针
return 0;
}
/*查找数据出现在链表中的位置*/
int findData(linknode_t *p,int n)
{
int post=0;
while(p->next!=NULL)
{
p=p->next;
post++;
if(p->data==n)
{
return post;
}
}
return -1;
}
/*清空链表*/
void clear(linknode_t *p)
{
linknode_t *pdel = NULL;
while(p->next!=NULL)
{
pdel = p->next;//创建一个指针指向被删除节点
p->next = pdel->next;
free(pdel);
pdel = NULL;
}
}
int main(int argc, const char *argv[])
{
linknode_t *p=creatEmpty(p);
if(p==NULL)
{
printf("p is NULL");
return -1;
}
show(p);
printf("length is %d\n",getLength(p));
printf("%d\n",isEmpty(p));
insertInto(p,1,11);
insertInto(p,2,22);
insertInto(p,3,33);
insertInto(p,4,44);
insertInto(p,5,55);
show(p);
insertInto(p,3,1000);
show(p);
deleteFrom(p,3);
show(p);
int res=findData(p,22);
if(res==-1)
{
printf("不存在\n");
}
else
{
printf("%d\n",res);
}
clear(p);
show(p);
printf("length is %d\n",getLength(p));
free(p);
return 0;
}
单向循环链表(首尾相连的环)
链表最后一个结点上存储了第一个结点的地址,就成为单向循环链表。
单向链表的应用
(1)尾插法
每次都是在链表的最后一个结点的最后一个位置进行插入操作。
(2)约瑟夫问题(循环杀死猴子,找到最后的猴王)
#include <stdio.h>
#include <stdlib.h>
typedef struct node
{
int data;
struct node *next;
}linklist_t;
int main()
{
int all_num; //猴子总数
int start_num; //开始报数的猴
int kill_num; //数到几杀死一个猴
printf("请输入总数 开始报数的猴 数到几杀死一个猴\n");
scanf("%d%d%d",&all_num,&start_num,&kill_num);
//将1-8使用尾插法形成一个环(无头)
linklist_t* phead = (linklist_t*)malloc(sizeof(linklist_t));
if(NULL == phead)
{
printf("phead malloc failed\n");
return -1;
}
phead->data = 1;
phead->next = NULL;
linklist_t* ptail = phead; //只有一个结点,既是头也是尾
//2-8用尾插连到1后面
int i;
for(i = 2;i <= 8 ;i++)
{
linklist_t* pnew = (linklist_t*)malloc(sizeof(linklist_t));
if(NULL == pnew)
{
printf("pnew malloc failed\n");
return -1;
}
pnew->data = i;
pnew->next = NULL;
ptail->next = pnew;
ptail = pnew; //新结点变成尾结点
}
ptail->next = phead; //将首尾相连形成环
//将头指针移动到开始报数的猴子身上
for(i = 0;i < start_num-1;i++)
{
phead = phead->next;
}
linklist_t* pdel = NULL;
//循环杀猴
while(phead != phead->next) //只剩下一只猴的时候结束循环
{
//将头指针移动到删除位置的前一个位置
for(i = 0;i < kill_num-2;i++)
{
phead = phead->next;
}
pdel = phead->next; //定义指针指向被删除结点
phead->next = pdel->next; //跨过删除结点
printf("%d kill out!\n",pdel->data); //打印出局的猴
free(pdel); //释放删除结点
pdel = NULL;
phead = phead->next; //删除一个节点,下一局从删除结点的后一个位置开始报数
}
printf("%d is monkey King\n",phead->data); //打印最后的猴王
free(phead);
return 0;
}
双向链表
//双向链表的定义
typedef struct node
{
int data; //数据域
struct node* pri //指针域指向上一结点
struct node* next; //指针域指向下一结点
}linklist_t;
顺序表和链表的区别
相同点:两者的逻辑结构都是线性的
不同点:顺序表采用顺序存储,数据在内存连续;
链表采用链式存储,数据在内存中不连续,通过指针链接到一起;
顺序表插入和删除麻烦,但查找速度快且可以随访问;
链表插入和删除方便,但是查找速度慢。