一、线性表的定义与特点
定义:n个数据类型相同的元素的有限集合,称为线性表。
特点:对于非空的线性表,q1存在唯一的一个第一个数据元素;q2存在唯一的一个最后一个数据元素;q3每个中间元素只有一个前驱和一个后继
二、线性表的顺序表示
线性表的顺序表示又称作顺序表,对于连续的一串数据,采用在内存单元上一串连续的内存进行储存。
顺序表基于数组进行数据储存。
cpp
#include <stdio.h>
#include <stdlib.h>
//顺序表的初始化
#define MAX 100
typedef int elemtype;
typedef struct
{
elemtype data[MAX];//seqlist第一个元素是数组,第二个是一个整数
int length;
}seqlist;
void initlist(seqlist *L)//顺序表的初始化
{
L->length=0;
}
int main(int argc, char const *argv[])
{
seqlist list;
initlist(&list);
return 0;
}
//*************************************************************
//顺序表在尾部插入元素
int appendelem(seqlist *L,elemtype e)//参数一是指向seqlist类型结构体的指针,参数二是要写入的数据
{
if(L->length>=MAX)
{
printf("顺序表已满\n");
}
else
{
L->data[L->length]=e;
L->length++;
}
}
//*************************************************************
//遍历
void listElem(seqlist *L)
{
for (int i = 0; i < L->length; i++)
{
printf("%d ", L->data[i]);
}
printf("\n");
}
//*************************************************************
//顺序表插入元素
int insertelem(seqlist *L,int pos,elemtype e)
{
if(pos<=L->length)
{//因为后面是空位可以直接进行替换,不用创造中间变量,从后遍历数组更佳
for(int i=L->length-1;i>=pos-1;i--)//第一个减一是为了进行移位,第二个减一是因为位置与数组下标错一位
{
L->data[i+1]=L->data[i];
}//循环后空出需要添加元素的位置
L->data[pos-1]=e;
L->length++;
}
}
//顺序表的插入时间复杂度为O(n)。
//*************************************************************
//顺序表删除元素
int deletelem(seqlist *L,int pos,elemtype e)//e的作用见下
{
if(pos<=L->length)
{//因为后面是空位可以直接进行替换,不用创造中间变量,从后遍历数组更佳
for(int i=pos;i>=L->length;i++)//第一个减一是为了进行移位,第二个减一是因为位置与数组下标错一位
{
L->data[i-1]=L->data[i];
}//循环后空出需要添加元素的位置
L->length--;
}
}
/* 在理解中,删除元素后,后面的元素占据了数组中前一个元素的位置,
但是实际上,在内存空间中,元素的绝对储存位置并没有发生变化,
也就是说删除元素依然在原来的内存地址中储存着,可以通过一个
指向这个地址的指针来调取这个删除元素(elemtype deldata;&deldata; *e=L->data[pos-1])
对数组来说,修改后的length后一位上储存的还是之前的最后一位的值,因为数组的内存空间是连续的*/
//*************************************************************
// 查找
int findElem(seqlist *L, elemtype e)
{
for (int i = 0; i < L->length; i++)
{
if (L->data[i] == e)
{
return i + 1;//返回位置值,是下标加1
}
}
return 0;
}
//*************************************************************
//顺序表的动态分配内存地址初始化(使用堆内存)
typedef struct
{
int *data;
int length;
}seqlist;
seqlist* initlist()//堆内存的初始化函数
{
seqlist *L=(seqlist*)malloc(sizeof(seqlist));//将结构体创建在堆内存中
L->data=(int*)malloc(sizeof(int)*100);//将data数组创建在堆内存中
L->length=0;
return L; //L指向堆内存中的顺序表
}
//之后可以定义seqlist *list =initlist();
/* 此时的list不再是上面的一个结构体变量,而是一个结构体指针,
也就是说上面编写的函数再一次使用时不需要对list取地址了。 */
三、线性表的链式表示(链表)
链表可以表示一个数据元素与后继元素的逻辑关系,为此,一个元素要储存其本身的信息和一个指向后继的信息,这两个部分的组合叫做一个节点。
节点含有两个域:数据域,存储数据元素信息:指针域,存储后继的储存位置,这个信息叫做指针或者链。
多个节点的链接构成一个链表。
链表的一个节点用结构体来表示。
(一)单链表
cpp
//单链表
typedef int ElemType;
typedef struct node
{
ElemType data;
struct node *next;
}Node;
//初始化链表
Node* initList()
{
Node *head=(Node*)malloc(sizeof(Node));
head->data=-1;
head->next=NULL;
return head;
}
//头插法
int insertHead(Node*L,ElemType e)
{
Node *p=(Node*)malloc(sizeof(Node));
p->data=e;
p->next=L->next;
L->next=p;
return 1;
}
//遍历
void listNode(Node*L)
{
Node *p = L->next; // 从第一个有效节点开始
while(p!=NULL)
{
printf("%d\n",p->data);
p=p->next;
}
printf("\n");
}
//获取尾部节点
Node* get_tail(Node*L)
{
Node *p = L; // 从链表头开始遍历
while (p->next!=NULL)
{
p=p->next;
}
return p;
}
//尾插法
Node* insertTail(Node *tail,ElemType e)
{
Node *p=(Node*)malloc(sizeof(Node));
p->data=e;
tail->next = p; // 核心:将新节点链接到原尾节点后
p->next=NULL;
return p;
}
//指定位置插入
int insertNode(Node *L,int pos,ElemType e)
{
Node *p=L;//保存前驱节点
int i=0;
while (i<pos-1)
{
i++;
p=p->next;
if (p==NULL)
{
return 0;
}
}
//需要插入的新节点
Node *q=(Node*)malloc(sizeof(Node));
q->data=e;
q->next=p->next;
p->next=q;
return 1;
}
//指定位置删除
int deleteNode(Node *L,int pos)
{
Node *p=L;//要删除的指针前驱
int i;
while (i<pos-1)
{
i++;
p=p->next;
if (p==NULL)
{
return 0;
}
}
Node *q=p->next;
p->next=q->next;
free(q);
return 1;
}
//获取链表长度
int listlength(Node *L)
{
Node *p=L->next;
int len=0;
while (p!=NULL)
{
p=p->next;
len++;
}
return len;
}
//释放链表
void freeList(Node *L)
{
Node *p=L->next;
Node *q;
while (p!=NULL)
{
q=p->next;
free(p);
p=q;
}
L->next=NULL;
}
//主函数
int main(int argc, char const *argv[])
{
Node *list=initList();
Node *tail=get_tail(list);
tail=insertTail(tail,10);
tail=insertTail(tail,20);
tail=insertTail(tail,30);
listNode(list);
insertNode(list,2,15);
listNode(list);
deleteNode(list,2);
listNode(list);
printf("%d\n",listlength(list));
return 0;
}
(二)循环链表
cpp
//*************************************************************
//单向循环链表
//判断是否成环
int isCycle(Node *head)//原理:不成环的链表的两个不同速度的指针不可能相遇
{
Node *fast=head;
Node *slow=head;
while (fast!=NULL&&fast->next!=NULL)
{
fast=fast->next->next;
slow=slow->next;
if (fast==slow)
{
return 1;
}
}
return 0;
}
//寻找环的入口
//原理:先判断环的节点数,让快指针先走这个数字,然后两个指针同时走,相遇的地址就是环的入口
Node* findBegin(Node *head)
{
Node *fast=head;
Node *slow=head;
while (fast!=NULL&&fast->next!=NULL)
{
fast=fast->next->next;
slow=slow->next;
if (fast==slow)//说明存在环
{
int count=1;
while (fast->next!=slow)//判断节点个数
{
count++;
fast=fast->next;
}
fast=head;//两个指针回到头结点
slow=head;
for (int i = 0; i < count; i++)//让快指针先走count步数
{
fast=fast->next;
}
while (fast!=slow)//两个指针同时行动,直到相遇
{
fast=fast->next;
slow=slow->next;
}
return slow;//返回入环点
}
}
return NULL;
}
(三)双向链表
cpp
//双向链表
typedef int Elemtype;
typedef struct node
{
Elemtype data;
struct node *next,*prev;
}Node;
//初始化链表
Node* initList()
{
Node *head=(Node*)malloc(sizeof(Node));
head->data=-1;
head->next=NULL;
head->prev=NULL;
return head;
}
//头插法
int insertHead(Node *L,Elemtype e)
{
Node *p=(Node*)malloc(sizeof(Node));
p->data=e;
p->prev=L;
p->next=L->next;
if (L->next!=NULL)
{
L->next->prev=p;
}
L->next=p;
return 1;
}
//遍历
void listNode(Node*L)
{
Node *p = L->next; // 从第一个有效节点开始
while(p!=NULL)
{
printf("%d\n",p->data);
p=p->next;
}
printf("\n");
}
//尾插法
int insertTail(Node *L,Elemtype e)
{
Node *p=(Node*)malloc(sizeof(Node));
p->data = e;
p->next = NULL; // 新节点作为尾节点,next指向NULL
p->prev = L; // 新节点的前驱指向原尾节点
L->next = p; // 原尾节点的后继指向新节点
return 1;
}
//在指定位置插入数据
int insertNode(Node *L,int pos,ElemType e)
{
Node *p=L;//保存前驱节点
int i=0;
while (i<pos-1)
{
i++;
p=p->next;
if (p==NULL)
{
return 0;
}
}
//需要插入的新节点
Node *q = (Node*)malloc(sizeof(Node));
q->data = e;
q->prev = p;
q->next = p->next;
p->next->prev = q;
p->next = q;
return 1;
}
//指定位置删除
int deleteNode(Node *L,int pos)
{
Node *p=L;//要删除的指针前驱
int i;
while (i<pos-1)
{
i++;
p=p->next;
if (p==NULL)
{
return 0;
}
}
Node *q=p->next;
p->next=q->next;
q->next->prev=p;
free(q);
return 1;
}
int main(int argc, char const *argv[])
{
Node *list=initList();
insertHead(list,10);
insertHead(list,20);
insertTail(list,30);
insertTail(list,0);
insertTail(list,60);
listNode(list);
return 0;
}
四、线性表的应用
cpp//例题,寻找链表倒数某一位上的值,解法:快慢双指针 typedef int ElemType; typedef struct node { ElemType data; struct node *next; }Node; //初始化链表 Node* initList() { Node *head=(Node*)malloc(sizeof(Node)); head->data=-1; head->next=NULL; return head; } int findNodeFS(Node *L,int k) { Node *fast=L->next; Node *slow=L->next; for(i=0;i<k;i++) { fast=fast->next; } while (fast!=NULL) { fast=fast->next; slow=slow->next; } printf("%d %d\n",k,slow->data); return 1; }
cpp//两个字符串结尾共用相同,寻找相同首字母 Node* findIntersectionNode(Node *headA,Node *headB) { if (headA==NULL||headB==NULL)//判断是否存在空链表 { return NULL; } int lenA=0,lenB=0;//分别遍历AB链表,获得两个链表的长度 Node *p=headA; while (p!=NULL) { p=p->next; lenA++; } p=headB; while (p!=NULL) { p=p->next; lenB++; } Node *f; Node *s; int step; step=fabs(lenA-lenB);//计算两个链表长度的差值 if (lenA>lenB)//选择快慢指针 { f=headA; s=headB; } else { s=headA; f=headB; } for (int i = 0; i < step; i++)//让快指针先走step步 { f=f->next; } while (f!=s)//两个指针同时走,直到找到相同地址 { f=f->next; s=s->next; } return f;//返回地址 }

