线性表
线性结构的特点就是:在数据元素非空有限集合中
存在唯一的一个被称为”第一个”的数据元素;
存在唯一的一个被称作“最后一个”的数据元素
除了第一个外,集合中的每个数据元素均只有一个前驱
除了最后的一个外,集合中的每个元素均只有一个后继。
线性表的顺序存储结构,在存,读数据的时候,不管在那个位置,时间复杂度都是O(1),而在插入或删除的时候,时间复杂度都是O(n);
这就说明,它比较合适元素个比较稳定,不经常插入和删除元素,而更多的操作就是存取数据
线性表优点 无需为表中元素之间的逻辑关系而增加额外的存储空间,可以快速的存取表中的任意元素。
缺点就是在增加元素和删除元素的时候需要移动大量的元素,这就需要耗费很多的时间;
实现的代码如下:
mylist.h
#ifndef __MYLIST__H
#define __MYLIST__H
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
#define MAXSIZE 20
typedef int status;
typedef int ElemType;
//使用C语言描述顺序链表
typedef struct
{
ElemType data[MAXSIZE];
int length; //线性表当前长度
}SqList;
//C语言描述顺序链表的操作算法
void unionL(SqList *La,SqList Lb);
status GetElem(SqList I,int i,ElemType *e);
status ListInsert(SqList *L,int i,ElemType e);
status ListDelete(SqList *L,int i,ElemType *e);
#endif //define MYLIST.H
/************************************************************
*@数据结构与算法
*
***********************************************************/
#include
#include "mylist.h"
int main()
{
printf("true = %d",TRUE);
printf("hello world!\n");
return 0;
}
//La表示A的集合,Lb表示b的集合
void unionL(SqList *La,SqList Lb)
{
int La_Len,Lb_Len,i;
ElemType e;
La_Len = ListLength(*La);
Lb_Len = ListLength(Lb);
for(i = 1; i
{
GetElem(Lb,i,&e);
if(!LocateElem(*La,e))
{
ListInsert(La,++La_Len,e);
}
}
}
//获取线性表第i个元素
//status 函数类型
status GetElem(SqList I,int i,ElemType *e)
{
if(L.length == 0 || i < 1 || i > L.length)
{
return ERROR;
}
*e = L.data[i-1];
return OK;
}
//插入新元素算法
//操作结果:在L中第i个位置之前插入新的数据元素e,L长度+1
//线性表是从1开始的
status ListInsert(SqList *L,int i,ElemType e)
{
int k;
if(L->length == MAXSIZE) //线性表已经填满
{
return ERROR;
}
if(i<1 || i>L->length+1) //当i不在范围之内
{
return ERROR;
}
if(i < L->length) //若插入数据位置不在表尾
{
/* 将要插入位置后的数据元素向后移动一位 */
for(k = L->length-1;k >= i - 1; k--)
{
l->data[k+1] = L->data[k];
}
}
L->data[i-1] = e;
L->length++;
return OK;
}
//删除操作
/* 操作结果,删除L的第i 个数据元素,并用e返回其值,L的长度-1 */
//实现的过程和插入
status ListDelete(SqList *L,int i,ElemType *e)
{
int k;
if(L->length == 0)
{
return ERROR;
}
if(i < 1 || i > L->length)
{
return ERROR;
}
*e = L->data[i-1];
if(i < L->length)
{
for(k = i;k < L->length; k++)
{
L->data[k-1] = L->data[k];
}
}
L->length--;
return OK;
}
缺点的解决方案:
头指针是指向链表的第一个节点的指针,若链表有头结点,则是指向头结点之前指向头结点的指针;
头指针具有标识作用所以常用头指针冠以链表的名字(指针变量的名字);
无论链表是否为空,头指针不能为空,
头指针是链表的关键元素;
头结点是为了操作的统一和方便而设立的,放在第一个元素的节点之前,其数据域一般无意义(单页可以用来存放链表的长度)。
有了头结点,在对第一元素节点前插入节点和删除第一节点来操作就与其它节点的操作就统一了;
头结点不一定是链表的必要元素;
在单链表中,取得第i个元素必须从头指针开始寻找,因此,单链表是非随机存取的存储结构;
实现单链表的GetElem函数如下:
//函数GetElem在单链表中的实现
Status GetElem(LinkList,int i,ElemType &e)
{
//L为带节点的单链表指针。
//当第i个元素存在时,其值赋给e并返回OK,否则返归ERROR
LinkList p = L->next;
int j = 1;
while(p && j
{
p = p->next;
++j;
}
if(!p || j > i)return ERROR;
e = p->data;
return OK;
} //GetElem
//实现单项链表的插入数据函数
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
#define MAXSIZE 20
typedef int status;
typedef int ElemType;
/
## 线性表单链表 ##
//使用C语言可以使用结构体来描述单链表
typedef struct Node
{
ElemType data; //数据域
struct Node* Next; //指针域
}Node* LinkList;
数据结构教会你,在存储数据的时候,你会考虑到使用不同给的数据结构实现不同给的数据存储,要是直接存储就是用数组,但是存储的数据需要频繁的插入和删除的话就是用单链表这样在使用的时候能够快速的插入数据并且在一次插入多个数据的时候会有明显的优势。
//单链表在实现数据的频繁插入和删除的时候会有明显的优势
//比如在i位置插入N个元素单链表的复杂度为O(n)+O(1)
//实现单项链表的插入函数
//插到第五个位置 应该放到第四个数据的后面
status ListInsert(LinkList &L,int i,ElemType e)
{
//带头节点单链表L中的第i 个位置之前插入元素e
LinkList p = L;
LinkList s = NULL;
int j = 0;
//寻找到第i个位置
while(p && j < i - 1) //寻找第i - 1结点
{
p = p->next;
++j;
}
if(!p || j > i - 1)return ERROR; //i小于1或大于表加1
s = (LinkList)malloc(sizeof(Node)); //生成新节点
//将第s放在第i个位置
s->data = e; //将s的data数据赋值为e
s->next = p->next; //将s->next指向p原来指向的元素
p->next = s;// 将p->next指向s结点,这样就实现在p处添加元素,并且指向原来的元素,原来的元素原来的上游指针指向该元素
return OK;
}
//实现单向链表的删除 并释放删除结点的内存空间
//实现单链表的删除操作
status ListDelete(LinkList &L,int i,ElemType &e)
{
//在带头结点的单线性表L中,删除第i个元素,并由e返回其值
LinkList p = L;
int j = 0;
while(p->next && j < i - 1) //寻找第i个结点并;令p指向p指向其前驱
{
p = p->next;
++j;
}
if(!(p->next) || j > i-1)return ERROR; //删除位置不合理
//当p->next不存在的时候说明遍历到结尾还诶有遍历到所要找的元素就返回ERROR
q = p->next;
//使用中间变量是为了实现在p->next指向p->next->next之后还能释放原来p->next指针指向的结点
p->next = q->next; //删除并释放结点
e = q->data;
free(q);
return OK;
}//ListDelete
像单链表一样拥有很高的灵活度;
单链表的整表创建:
对于单链表来说,它所占用的空间到校和位置是不需要预先分配和划定的,它 可以根据系统的情况和实际的需要即时生成。
所以单链表的创建算法思路如下:
声明一结点p和计数器变量i;
初始化一空链表L;
让头结点的指针指NULL,即建立一个带头的结点的单链表
循环实现后继结点的赋值和插入
下面就是建立单链表的方式,实现的方式是在表头插入新元素实现链表的建立
//采用头插法建立单链表
void CreatListHead(LinkList *L,int n)
{
//建立表头结点的单链表L
LinkList p;
int i;
srand(time(0)); //初始化随机数种子 包含在头文件 #include中
*L = (LinkList)malloc(sizeof(Node));
L->next = NULL; //先建立带头结点的单链表
for(i = n;i > 0;--i)
{
p = (LinkList)malloc(sizeof(Node)); //生成新结点
p->data = rand()%100+1; //初始化新节点的数值
//scanf(p->data); //输入元素
p->next = L->next; //插入到表头
L->next = p;
}
}
注:srand()函数说明
rand函数在产生随机数前,需要系统提供的生成伪随机数序列的种子,rand根据这个种子的值产生一系列随机数。如果系统提供的种子没有变化,每次调用rand函数生成的伪随机数序列都是一样的。srand(unsigned seed)通过参数seed改变系统提供的种子值,从而可以使得每次调用rand函数生成的伪随机数序列不同,从而实现真正意义上的“随机”。通常可以利用系统时间来改变系统的种子值,即srand(time(NULL)),可以为rand函数提供不同的种子值,进而产生不同的随机数序列
#include/*用到了srand函数,所以要有这个头文件*/
#include
#define MAX 10
int main(void)
{
int number[MAX] = {0};
int i;
unsigned int seed;
scanf("%d",&seed);/*手动输入种子*/
srand(seed);
for(i = 0; i < MAX; i++)
{
number[i] = (rand() % 100);/*产生100以内的随机整数*/
printf("%d\n",number[i]);
}
printf("\n");
return 0;
}
//使用尾插法实现单链表的实现过程
//采用尾插法实现单链表
void CreatListHead(LinkList *L,int n)
{
//建立表头结点的单链表L
LinkList p,r;
int i;
srand(time(0)); //初始化随机数种子
*L = (LinkList)malloc(sizeof(Node)); //只有使用指向指针的指针才能将在函数中申请的内存使用到
r = *L;
(*L)->next = NULL; //先建立带头结点的单链表在这里加上括号就是用括号
//至于哪里使用->只要谁是LinkList后的所有的东西可以调用text
for(i = 1;i < n;i++)
{
p = (LinkList)malloc(sizeof(Node)); //生成新结点
p->data = rand()%100+1; //初始化新节点的数值
//scanf(p->data); //输入元素
r->next = p; //插入到表头 p是临时结点
r = p; //r只是一个中间转换的变量在使用的时候刚开始 r与*L相同代替*L但是不是*L
//在使用完之后要保证r始终与链表结尾的结点保持一致这样在下一次for循环的时候r还是代表
//链表的最后的结点 能够一直代表最后的结点
}
r->next = NULL;
}
当单链表不需要的时候是用链表删除将单链表删除
释放内存方便其他数据使用内存单元
//单链表整体删除
//当我们不使用的时候将单链表整体删除
status ClearList(LinkList *L)
{
LinkList p,q;
p = (*L)->next;
while(p)
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL;
return OK;
}
静态链表实现
//使用数组描述静态链表
//线性表的静态链表存储结构
#define MAXSIZE 1000
typedef struct
{
ElemType data; //数据
int cur; //游标(Cursor)
}Component,StaticLinkList[MAXSIZE];
静态链表实现
/
//静态链表
//数组描述的链表叫做静态链表
//静态练的初始化 使用数组实现单链表的操作
//增加了下标就可以实现单链表 的上一个只是下一个
//静态链表的特点就是:
//使用数组模拟静态链表
//数组的第一个元素 的游标指向 第一个空闲的下标
//数组的最后一个元素 的游标指向第一个元素的下标
//数据项的第一个数据的游标指向第二个
//数据项的最后一个数据的游标指向 数组第一个元素
status InitList(StaticLinkList space)
{
int i;
for(i = 0;i < MAXSIZE - 1;i ++)
{
space[i].cur = i + 1; //当游标是空的时候cur的上一位指向下一位
}
space[MAXSIZE - 1].cur = 0;
return OK;
}
//实现的过程
//静态链表的插入擦操作
//首先是获取空闲分量的下标
//静态链表只是模拟的单链表在实现的过程中要是malloc一个空闲的下标只需要获取第一个空闲的下标那么就能将后面的空闲的内存拿来使用
int Malloc_SLL(StaticLinkList space)
{
int i = space[0].cur; //将第一个下标指向的下标分给 i
if(spce[0].cur)
{
space[0].cur = space[i].cur; //将原来space[i].cur 指向的下标给space[0].cur
}
return i;
}
//函数实现在静态链表L中第i个元素之前插入新的数据元素e
//i 是说明插入在的几个元素之前
sattus LinkListInsert(StaticLinkList L,int i,ElemType e)
{
int j,k,l;
k = MAXSIZE-1; //数组的最后一个元素
if(i < 1 || i > ListLength(L)+1)
{
return ERROR;
}
j = Malloc_SLL(L); //获取空闲的第一个下标
//可以使用这一个空闲的下标找到所有的空闲的下标
if(j)
{
L[j].data = e;
for(l = 1;l <= i-1;l++)
{
k = L[k].cur; //L[k].cur 实现从游标到下标的挨个遍历
}
L[j].cur = L[k].cur; //将原先数据数据第一个结点指向的数据 送给新生成的结点指向
L[k].cur = j; //将原先的数据结点指向新生成的结点 这样就完成的数据的插入操作
return OK;
}
return ERROR;
}
//当链表中有元素要被删除的时候实现备用将数据的位置归并到备用链表中
//将首元素的地址指向新的其实元素
//实现元素之间的对接
//例如下面删除C元素 即删除第三个数据元素
status ListDelete(StaticLinkList L,int i)
{
int j,k;
if(i < 1 || i > ListLength(L)) //对 i 的合法性进行检测
{
return ERROR;
}
k = MAXSIZE - 1; //因为数组的最后一个的游标指向数据元素的第一个
for(j = 1; j <= i - 1; j++)
{
k = L[k].cur; //通过循环将要删除的 的元素取出来
//例如删除第二个元素i =3; 则k1 = 1,k2 = 5;
}
j = L[k].cur; //实现指定元素的删除
L[k].cur = L[j].cur;
Free_SLL(L,j);
return OK;
}
//将下标为k的空闲结点回收到备用链表
void Free_SLL(StaticLinkList space,int k)
{
space[k].cur = space[0].cur;
space[0].cur = k;
}
//返回L中数据元素的个数
int ListLength(StaticLinkList L)
{
int j = 0;
int i = L[MAXSIZE-1].cur;
while(i)
{
i = L[i].cur; //因为最后一个元素指向第一个数据元素 最后一个数据元素指向第0号数组元素
//因此当 L[i].cur == 0的时候就说明已经遍历到数据元素的最后一个元素
//最终返回的是数据元素的大小
j ++;
}
return j;
}
特点:
在插入和删除元素的时候,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构的中的插入和删除操作需要移动大量数据元素的缺点
缺点:没有解决连续存储分配数组难以定长的问题;
失去了顺序存储结构随机存取的特性;
静态单链表结尾
插入结点的综合练习:
#include
#include
typedef struct CLinkList
{
int data;
struct CLinkList *next;
}node;
void ds_init(node **pNode)
{
int item;
node *temp;
node *target;
printf("请输入结点的值,输入0完成初始化\n");
while(1)
{
scanf("%d",&item);
fflush(stdin);//清空缓冲区
if(item==0)
{
return;
}
if((*pNode)==NULL)
{
//循环链表只有一个结点头结点
*pNode=(node*)malloc(sizeof(struct CLinkList));
if(!(*pNode))
exit(0); //分配内存失败强制退出
(*pNode)->data=item; //输入的数据给data
(*pNode)->next=*pNode; //循环链表
}
else
{
//找到next指向第一个结点的结点
for(target=(*pNode);target->next!=(*pNode);target=target->next);
//循环链表的插入在链表的结尾处插入找打链表尾部
//生成一个新结点
temp=(node *)malloc(sizeof(struct CLinkList));
if(!temp)
exit(0);
//新生成的为尾结点
temp->data=item;
temp->next=*pNode; //指向头结点
target->next=temp; //原来的尾结点指向新的尾结点
}
}
}
void ds_insert(node **pNode,int i)
{
node *temp;
node *target;
node *p;
int item;
int j=1;
printf("输入要插入结点的值:\n");
scanf("%d",&item);
if(i==1)
{
temp=(node *)malloc(sizeof(struct CLinkList));
if(!temp)
{
exit(0);
}
temp->data=item;
for(target=(*pNode);target->next!=(*pNode);target=target->next);
temp->next=(*pNode);
target->next = temp;
*pNode = temp;
}
else
{
target=*pNode;
for(j=1;j
{
target=target->next;
}
temp=(node *)malloc(sizeof(struct CLinkList));
if(!temp)
{
exit(0);
}
temp->data=item;
p=target->next;
target->next=temp;
temp->next=p;
}
}
void ds_delete(node **pNode,int i) //i是要删除的位置
{
node *target;
node *temp;
int j=1;
if(i==1)
{
//删除的是第一个结点
//找出尾结点
for(target=(*pNode);target->next!=(*pNode);target=target->next); //使用for循环找出尾结点
temp=(*pNode); //先把 第一个结点的指针记下来
*pNode=(*pNode)->next;
target->next=*pNode;
free(temp);
}
else
{
//定位再删除
target=*pNode;
for(j=1;j
{
target=target->next;
}
temp=target->next;
target->next=temp->next;
free(temp);
}
}
int ds_search(node *pNode,int elem) //查找元素data所在位置
{
node *target;
int i=1;
for(target=pNode;target->data!=elem && target->next!=pNode;++i)
{
target=target->next;
}
if(target->next==pNode) //表中不存在该元素
return 0;
else
return i; //
}
void ds_traverse(node *pNode)
{
node *temp;
temp = pNode;
printf("***********Á´±íÖеÄÔªËØ******************\n");
do
{
printf("%4d ", temp->data);
}while((temp = temp->next) != pNode);
printf("\n");
}
int main()
{
node *pHead = NULL;
char opp;
int find;
printf("1.³õʼ»¯Á´±í \n\n2.²åÈë½áµã \n\n3.ɾ³ý½áµã \n\n4.·µ»Ø½áµãλÖà \n\n5.±éÀúÁ´±í \n\n0.Í˳ö \n\nÇëÑ¡ÔñÄãµÄ²Ù×÷£º");
while(opp!= '0')
{
scanf("%c", &opp);
switch(opp)
{
case '1':
ds_init(&pHead);
printf("\n");
ds_traverse(pHead);
break;
case '2':
printf("ÊäÈëÐèÒª²åÈë½áµãµÄλÖã¿");
scanf("%d", &find);
ds_insert(&pHead, find);
printf("ÔÚλÖÃ%d²åÈëÖµºó£º\n", find);
ds_traverse(pHead);
printf("\n");
break;
case '3':
printf("ÊäÈëÐèҪɾ³ýµÄ½áµãλÖã¿");
scanf("%d", &find);
ds_delete(&pHead, find);
printf("ɾ³ýµÚ%d¸ö½áµãºó:\n", find);
ds_traverse(pHead);
printf("\n");
break;
case '4':
printf("ÄãÒª²éÕÒµ¹ÊýµÚ¼¸¸ö½áµãµÄÖµ£¿");
scanf("%d", &find);
printf("ÔªËØ%dËùÔÚλÖãº%d\n", find, ds_search(pHead, find));
//ListTraverse(L);
printf("\n");
break;
case '5':
ds_traverse(pHead);
printf("\n");
break;
case '0':
exit(0);
}
}
return 0;
}
魔术师发牌问题:
#include
#include
#define CardNumber 13
typedef struct node
{
int data;
struct node *next;
}sqlist,*linklist;
linklist CreateLinkList()
{
linklist head=NULL;
linklist s,r;
int i;
r=head;
for(i=0;i<=CardNumber;i++)
{
s=(linklist)malloc(sizeof(sqlist));
s->data=0;
if(head==NULL)
head=s;
else
r->next=s;
r=s;
}
r->next=head;
return head;
}
void Magician(linklist head)
{
linklist p;
int j;
int Countnumber=2;
p=head;
p->data=1;
while(1)
{
for(j=0;j
{
p=p->next;
if(p->data!=0)
{
p->next;
j--;//Ö¸ÏòÖظ´µÄÅÆ£¬Ìø¹ý
}
}
if(p->data==0)
{
p->data=Countnumber;
Countnumber++;
if(Countnumber==14)
break;
}
}
}
void DestoryList(linklist* List)
{
linklist ptr=*List;
linklist buff[CardNumber];
int i=0;
while(i
{
buff[i++]=ptr;
ptr=ptr->next;
}
for(i=0;i
free(buff[i]);
*List=0;
}
int main()
{
linklist p;
int i;
p=CreateLinkList();
Magician(p);
printf("按如下顺序排列\n");
for (i=0;i
{
printf("黑桃%d ",p->data);
p=p->next;
}
DestoryList(&p);
return 0;
}
双向循环链表的实现测试
#include
#include
#define ok 1
#define ERROR 0
typedef char ElemType;
typedef int Status;
typedef struct DualNode
{
ElemType data;
struct DualNode *prior;
struct DualNode *next;
}DualNode,*DualLinkList;
Status InitList(DualLinkList *L)
{
DualNode *p, *q;
int i;
*L = (DualLinkList)malloc(sizeof(DualNode));
if(!(*L))
{
return ERROR;
}
(*L)->next = (*L)->prior =NULL; //刚开始生成一个空的链表 不能让指针乱指
p = (*L); //p现在是一个头结点的值
for(i = 0;i < 26; i ++)
{
q = (DualLinkList)malloc(sizeof(DualNode));
if(!q)
{
return ERROR;
}
q->data = 'A' + i;
q->prior = p;
q->next = p->next; //这样写程序更加灵活
p->next = q;
//让循环动起来
p = q;
}
p->next = (*L)->next;
(*L)->next->prior = p; //数据元素的第一个指向数据元素的最后一个 形成一个循环链表
return OK;
}
void Casear(DualLinkList *L,int i)
{
if(i > 0)
{
do
{
(*L) = (*L)->next;
}while(--i);
}
if(i < 0)
{
do
{
(*L) = (*L)->next;
}while(++i);
}
}
int main()
{
DualLinkList L;
int i, n;
printf("请输入一个整数可以为正也可以为负\n");
scanf("%d",&n);
printf("\n");
InitList(&L);
Casear(&L,n);
for(i = 0;i < 26;i++)
{
L = L->next;
printf("%c",L->data);
}
return 0;
}