在计算机科学中,线性结构被称为线性表
线性表是在数据元素的非空集合中:
①存在唯一的一个首元素;
②存在唯一的一个尾元素;
③除首元素外每个元素有且只有一个直接前驱;
④除尾元素外每个元素有且只有一个直接后续;
按存储方式分为顺序存储与链式存储
线性表的基本操作有:
初始化、插入、删除、遍历(即访问每一个元素,如打印所有信息)、查找、排序
顺序存储结构:用一组地址连续的存储单元 依次存放线性表的所有数据元素
特点1:逻辑关系上相邻的两个元素在物理位置上也相邻,因此,可以直接存取表中任意一个元素
特点2(缺点):①存储空间闲置、存储容量难以扩充;②当进行插入和删除操作时,存在大量数据元素移动
链式存储结构:①在链式存储结构中,数据元素的存储单元是不连续的。每个元素除自身信息外,还需要存储其后续元素的信息
②每个元素的存储映像称为结点,存放数据元素信息的域称为数据域,存放后续结点存储位置的域称为指针域,每个结点由数据域和指针域构成
③用指针相连的结点序列称为链表
链表,别名链式存储结构或单链表,用于存储逻辑关系为 “一对一” 的数据。链表中每个数据的存储都由以下两部分组成:
1.数据元素本身,其所在的区域称为数据域。
2.指向直接后继元素的指针,所在的区域称为指针域。
单链表存储结构中,有一首指针head指示链表中第一个结点的存储位置,同时,由于最后一个元素没有直接后续,所以单链表最后一个结点的指针域为空(NULL),而当head为NULL时,head所指向的单链表为空表,其表长length=0
单链空表逻辑状态图如下:
单链表一般逻辑状态图如下:
单链表结构体定义
//单链表的结构定义
typedef struct list
{
int data; //存放数据的地址
struct list *next;
}LIST_T;
单链表的初始化(创建头结点)
LIST_T*List_Init(int data)
{
LIST_T * head;
head = (LIST_T *)malloc(sizeof(LIST_T));
head->data=data;
head->next=NULL;
return head;
}
单链表的插入
void List_Add(LIST_T *head,int data)
{
LIST_T *pNode,*p1=head;
pNode=(LIST_T *)malloc(sizeof(LIST_T ));
while(p1->next != NULL )
{
p1=p1->next; //遍历链表,找到最末尾的节点
}
p1->next=pNode;
pNode->data=data;
pNode->next=NULL;
}
单链表结点位置的删除
//删除单链表中第index位置上的结点
int List_Del(LIST_T * head,int index)
{
LIST_T * p1;
LIST_T * p2; //要删除结点的临时结点
int nCount = 0;
p1=head;
while(p1->next != NULL)
{
if(nCount == index)
{
p2 = p1->next;
p1->next = p1->next->next;
free(p2);
}
p1=p1->next;
nCount++;
}
return nCount;
}
单链表修改结点
// 修改单链表L的第index个位置上的结点
void replace(LIST_T *head, int index, int data)
{
LIST_T * p1;
int nCount = 0;
p1=head;
while(p1->next != NULL)
{
if(nCount == index)
{
p1->data = data;
break;
}
p1=p1->next;
nCount++;
}
head = p1;
}
单链表结点打印
void Data_Print(LIST_T *head)
{
LIST_T *p1=head->next;
while(p1 != NULL)
{
printf("data = %d\n",p1->data);
p1=p1->next;
}
}
释放链表
LIST_T *List_Free(LIST_T *head)
{
LIST_T *ptr=head;
while(ptr!=NULL)
{
ptr=ptr->next;
free(head->data);//先释放数据存储的内存空间
free(head);//再释放链表节点的内存空间
head=ptr;
}
return head;
}
#include
#include
#include
/*结构体定义,增删改查,和上面单链表基本操作等同,这里省略*/
int main()
{
LIST_T *head;
//初始化链表
head=(LIST_T *)List_Init(NULL);//头节点不存储数据,参数为NULL
//添加链表节点
List_Add(head,1);
List_Add(head,2);
Data_Print(head);
replace(head,1,10);
Data_Print(head);
return 0;
}
执行结果:
data = 1
data = 2
data = 10
data = 2
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。其结构图为:
双向链表中各节点包含以下 3 部分信息:
指针域:用于指向当前节点的直接前驱节点;
数据域:用于存储数据元素。
指针域:用于指向当前节点的直接后继节点;
1.每次在插入或删除某个节点时, 需要处理四个节点的引用, 而不是两个. 实现起来要困难一些
2.相对于单向链表, 必然占用内存空间更大一些.
3.既可以从头遍历到尾, 又可以从尾遍历到头
双向链表的结构体定义
typedef struct list{
struct list *pre;
int data;
struct list *next;
}LIST_T;
双向链表的初始化(创建头结点)
LIST_T*List_Init()
{
LIST_T * head;
head = (LIST_T *)malloc(sizeof(LIST_T));
if(head == NULL)
{
printf("malloc error!\r\n");
return NULL;
}
head->data=0;
head->pre=NULL;
head->next=NULL;
return head;
}
双向链表的插入
/*在尾部位置的插入data节点*/
void InsertNode(LIST_T * head,int data)
{
/*新建数据域为data的结点*/
LIST_T * pNode,*p=head;
pNode=(LIST_T *)malloc(sizeof(LIST_T ));
while(p->next)
{
p=p->next; //遍历链表,找到最末尾的节点
}
if (p->next==NULL)
{
p->next=pNode;
pNode->pre=p;
pNode->next=NULL;
pNode->data = data;
}
}
双向链表数据结点位置的删除
LIST_T * DeleteList(LIST_T * head,int data)
{
LIST_T * temp=head;
/*遍历链表*/
while(temp)
{
if (temp->data==data)
{
/*判断是否是头结点*/
if(temp->pre == NULL)
{
head=temp->next;
temp->next = NULL;
free(temp);
return head;
}
/*判断是否是尾节点*/
else if(temp->next == NULL)
{
temp->pre->next=NULL;
free(temp);
return head;
}
else
{
temp->pre->next=temp->next;
temp->next->pre=temp->pre;
free(temp);
return head;
}
}
temp=temp->next;
}
return head;
}
双向链表修改结点数据
// 修改链表L的第index个位置上的结点数据
LIST_T *replace(LIST_T * p,int index,int newdata)
{
LIST_T * temp=p;
int i=1;
/*遍历到被删除结点*/
for(i=1;i<index; i++)
{
temp=temp->next;
}
temp->data=newdata;
return p;
}
双向链表结点数据查找
/*head为原双链表,elem表示被查找元素*/
int FindList(LIST_T * head,int elem)
{
/*新建一个指针t,初始化为头指针 head*/
LIST_T * temp=head;
int i=1;
while(temp->next!=NULL)
{
if (temp->data==elem)
{
return i;
}
i++;
temp=temp->next;
}
/*程序执行至此处,表示查找失败*/
return -1;
}
双向链表数据打印
/*输出链表的功能函数*/
void PrintList(LIST_T * head)
{
LIST_T * temp=head;
printf("链表数据有:");
while (temp)
{
/*如果该节点无后继节点,说明此节点是链表的最后一个节点*/
if (temp->next==NULL)
{
printf("%d\n",temp->data);
}
else
{
printf("%d->",temp->data);
}
temp=temp->next;
}
}
#include
#include
#include
/*结构体定义,增删改查,和上面双向链表基本操作等同,这里省略*/
int main()
{
LIST_T * head=NULL;
//创建双链表
head=List_Init();
printf("新创建双链表为\t");
PrintList(head);
//插入结点
InsertNode(head,1);
printf("在表中插入元素 1\t");
PrintList(head);
InsertNode(head,7);
printf("在表中插入元素 7\t");
PrintList(head);
InsertNode(head,6);
printf("在表中插入元素 6\t");
PrintList(head);
//表中删除元素
head=DeleteList(head,6);
printf("表中删除元素 6\t\t\t");
PrintList(head);
//查找元素
printf("元素 1 的位置是\t:%d\n",FindList(head,1));
//修改元素
head = replace(head,1,6);
printf("表中第1个节点中的数据改为6\t");
PrintList(head);
return 0;
}
执行结果:
新创建双链表为 链表数据有:0
在表中插入元素 1 链表数据有:0->1
在表中插入元素 7 链表数据有:0->1->7
在表中插入元素 6 链表数据有:0->1->7->6
表中删除元素 6 链表数据有:0->1->7
元素 1 的位置是 :2
表中第1个节点中的数据改为6 链表数据有:6->1->7
逆序思想:
1、链表为空时:不需要逆序;
2、链表只有一个节点时:不需要逆序;
3、链表的节点为两个以及两个以上时:需要逆序
逆序代码:
LIST_T *link_reversed_order(LIST_T *p_head)
{
LIST_T *pf = NULL, *pb = NULL, *tmp = NULL;
pf = p_head;
if(p_head == NULL)
{
printf("链表为空,不需要逆序!\n");
return p_head;
}
else if(p_head->next == NULL)
{
printf("链表只有一个节点,不需要逆序!\n");
return p_head;
}
else
{
pf = p_head->next;
pb = pf->next; //pf->next要赋值空,不然反向会在最后两个结点形成环
pf->next = NULL;
free(p_head); //删掉头,不然反向链表会打印头结点数据NULL(例子这里为0)
while(pb != NULL)
{
tmp = pb;
pb = pb->next;
tmp->next = pf;
pf = tmp;
}
LIST_T *pNode; //链表反序后无头结点,这里自己建一个头
pNode=(LIST_T *)malloc(sizeof(LIST_T ));
pNode->data=NULL;
pNode->next = pf;
return pNode;
}
}
实例:
(结合上文单链表的操作)
int main()
{
LIST_T *head;
//初始化链表
head=(LIST_T *)List_Init(NULL);//头节点不存储数据,参数为NULL
//添加链表节点
List_Add(head,1);
List_Add(head,2);
List_Add(head,3);
Data_Print(head);
printf("\n");
head = link_reversed_order(head);
Data_Print(head);
return 0;
}
执行结果:
data = 1
data = 2
data = 3
data = 3
data = 2
data = 1