单链表结构
将线性表L={a0..........an-1}中各元素分布在存储器的不同存储块,称为结点,通过地址或指针建立他们之间的联系,这种存储结构为链表结构
通过下面这个例子来理解吧
结点类型描述
我们的结点类型主要由两种类型构成,所以我们要使用结构体来进行定义,首先是存放元素的数据域和存放下个结点地址的指针域。为了方便,我们还是使用了typedef来取别名
typedef int data_t;
//构造链表节点类型
typedef struct node_t{
data_t data;//数据域
struct node_t *next;//保存下一个节点的指针域
}linklist;
随后我们就可以创建链表,和链表表头,随后利用malloc函数为表头申请存储空间,并初始化。
//创建链表:创建链表头节点
linklist* create_linklist()
{
linklist *head=(linklist*)malloc(sizeof(linklist));//给头节点开辟空间
if(NULL==head)
return NULL;
head->data=-1;//初始化
head->next=NULL;//初始化
return head;
}
创建了头结点后方便我们用它去指向其他的结点!!!
单向链表的插入
算法思路:如下图
首先定义一个指针指向头结点,将我们需要插入的new结点开辟空间,在这个例子中我们要将新元素插入到位置pos为1的位置,假如我们要插入的位置为pos,那么就需要将指针p指向pos位置的前驱,此刻p->next里保存的是下一个结点的地址值,将其赋值给new->next,以免后一位地址丢失,随后将new结点的地址赋值给p->next,(此时p->next在等号左边表示当前结点的指针域空间,在等号右边就表示存储的下一个结点的地址值),相当于断开原来的连接,重新链接到new结点位置,如图所示便完成了元素的插入。
我们来看看代码
//按位置插入数据
int insert_by_pos(linklist *head,int pos,data_t val)
{
if(NULL==head)
return -1;
//判断位置合法性
int len=get_length_linklist(head);
if(pos<0 || pos>len)
return -1;
//准备新结点和辅助指针P
linklist *p=head;
//给新结点开辟空间
linklist* new=(linklist*)malloc(sizeof(linklist));
//给新结点初始化
new->data=val;
new->next=NULL;
//找到目标位置前一个位置
while(pos--) p=p->next;
//插入数据
new->next=p->next;//把目标后的结点位置接到新结点的指针域防止丢失
p->next=new;//把目标前一个的NEXT接到新结点
}
单向链表的删除
算法思路如下图:
代码如下:
//删除
int del_by_pos(linklist *head,int pos)
{
if(NULL==head) return -1;//判断空间是否开辟成功
if(1==linklist_is_empty(head))//判断链表是否为空
return -1;
int len=get_length_linklist(head);
if(pos <0 || pos>len-1)
return -1;
linklist *p=head;//将指针P指向头结点
linklist *q;//定义一个辅助指针
while(pos--) p=p->next;//将P指针指向要删除结点的前驱
q=p->next;//先把要删除结点的地址保存下来以免丢失
p->next=q->next;//将要删除结点的后继结点地址赋值给前驱的指针域
free(q);//释放删除结点的空间
q=NULL;
return 0;
}
单向链表的修改和查找
实现起来都很简单通过指针遍历即可:
//改变
int change_by_pos(linklist* head,int pos,data_t new_val)
{
//判断HEAD
if(NULL==head) return -1;
//判断位置合法性
int len=get_length_linklist(head);
if(pos<0 || pos>len-1) return -1;
//准备指针P遍历到POS-1的位置
linklist *p=head;
while(pos--) p=p->next;
//将POS位置值修改
p->next->data=new_val;
return 0;
}
//按位置查找
data_t find_by_pos(linklist* head,int pos)
{
//判断HEAD
if(NULL==head) return -1;
//判断位置合法性
int len=get_length_linklist(head);
if(pos<0 || pos>len-1) return -1;
//准备指针P遍历到POS-1的位置
linklist *p=head;
while(pos--) p=p->next;
//返回值
return p->next->data;
}
头插法以及链表的倒置
先插入的排到后面,我们只需要关心头结点即可!
算法思路如图:
代码:
//头插法
int insert_by_head(linklist *head,data_t val)
{
if(NULL==head) return -1;
linklist* new=(linklist*)malloc(sizeof(linklist));
new->data=val;
new->next=head->next;
head->next=new;
return 0;
}
//头插法实现逆序
int reverse_order_by_head(linklist* head)
{
if(NULL==head) return -1;
linklist *p;
linklist *q;
p=head->next;//将P指针指向第一个结点的地址,相当于备份
head->next=NULL;//将原链表清空
while(p!=NULL)//遍历链表直到末尾
{
q=p;//将当前要插入的结点地址保存到q
p=p->next;//将P指向下一个结点,以防后面数据丢失
q->next=head->next;//将当前结点插入到头结点之后
head->next=q;
}
return 0;
}
链表的打印
int show_linklist(linklist* head)
{
if(NULL==head) return -1;
if(1==linklist_is_empty(head))
return -1;
linklist *p=head->next;
while(p!=NULL)
{
printf("%d ",p->data);
p=p->next;
}
puts("");
}
单向循环链表
双向链表
我们先来看看双向链表的结点类型构造把:
//构造双向链表节点类型
typedef int data_t;
typedef struct d_node_t{
data_t data;
struct d_node_t *prior;
struct d_node_t *next;
}dlinklist;
双向链表的删除和插入算法如下图所示:
看看代码如何实现吧
//删除数据
int del_by_pos(dlinklist* head,int pos)
{
if(NULL==head) return -1;//判断head
int len=get_length(head);
if(1==dlinklist_is_empty(head)) return -1;
if(pos<0 ||pos>len-1)//判断位置合法性
return -1;
dlinklist *p=head;
dlinklist *q=NULL;//初始化
while(pos--)
p=p->next;
q=p->next;
if(q->next!=NULL)//判断删除数据位置是中间还是末尾
{
p->next=q->next;
q->next->prior=p;//删除中间
free(q);
q=NULL;
}
else//删除末尾
{
p->next=NULL;
free(q);
q=NULL;
}
return 0;
}
双向链表的插入:
代码如下:
//插入数据
int insert_by_pos(dlinklist* head,int pos,data_t val)
{
if(NULL==head) return -1;
//判断位置合法性
int len=get_length(head);
if(pos<0 || pos>len) return -1;
//准备指针P找到POS-1的位置
dlinklist* p=head;
while(pos--)
p=p->next;
//准备新结点
dlinklist* new=(dlinklist*)malloc(sizeof(dlinklist));
new->next=NULL;
new->prior=NULL;
new->data=val;
//判断插入位置在末尾还是在中间
if(p->next!=NULL)//p->next==NULL表示在末尾插入
{
new->next=p->next;//中间插入的核心代码
new->next->prior=new;
new->prior=p;
p->next=new;
}
else
{
new->prior=p;//末尾插入的代码
p->next=new;
}
}
双向循环链表
线性表的应用:Joseph问题
思路:先建立单向循环链表,从1-n依次赋值,定义一个指针L指向第一个节点位置,方便构成循环链表,利用循环遍历找到第k个节点位置,将r指针指向要删除结点的前驱,将p指向被删除的结点,以免丢失地址,删除结点,将r指向新一轮起点,重复只剩下最后一个。