数据结构:单向链表、单向循环链表、双向链表

一、概述

  • 链式存储结构是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。又称链表。

  • 链表特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。这就意味着,这些数据元素可以存在内存未被占用的任意位置(如图所示)。
    在这里插入图片描述
    在这里插入图片描述

  • 以前在顺序结构中,每个数据元素只需要存数据元素信息就可以了。现在链式结构中,除了要存数据元素信息外,还要存储它的后继元素的存储地址。

  • 把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域,指针域中存储的信息称做指针或链;这两部分信息组成 数据元素的存储映像,称为结点(node)

  • 链表中第一个结点的存储位置叫做头指针

  • 在单链表中的第一个结点前附设一个结点,叫头结点
    在这里插入图片描述
    在这里插入图片描述

  • 头结点不是链表的必要元素

  • 链表存储如下图所示
    在这里插入图片描述

二、单向链表

  • C结构体
typedef int  datatype;    //元素数据域类型可为任一类型,不局限于int
struct node
{
	datatype data;
	struct node *next;
};
2.1 有头结点的实现方式

在这里插入图片描述
(1)创建链表,生成一个头结点

struct node *list_create(void)
{
	struct node *list;
	list = (struct node *)malloc(sizeof(struct node));
	if (list == NULL)
		return NULL;
	list->next = NULL;
	
	return list;
}

(2) 插入
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
关键代码 : s->next = p->next; p->next = s; 切忌这两个顺序不能调换

  • s 即为new;
//按位置插入
int list_insert_at(struct node *list, int i, datatype data)
{
	int j = 0;
	struct node *p = list;
	
	if (i < 0)
		return -1;
		
	while (p != NULL && j < i) {   //寻找第i-1 个结点
		p = p->next;
		j++;
	}
	if (p == NULL || j > i)
		return -2;   //寻找第i 个元素不存在
		
	struct node *new = (struct node *)malloc(sizeof(struct node));  // 生成新结点
	if (new == NULL)
		return -3;
		
	new->data = data; 
	
	new->next = p->next;  //将p的后继结点赋值给new的后继
	p->next = new;		// 将new赋值给p的后继
	
	return 0;
}

//按序插入
int list_order_insert(struct node *list, datatype *data)
{
	struct node *p = list, *q;
	while (p->next->data < *data && p->next)
		p = p->next;
	q = (struct node *)malloc(sizeof(struct node));
	if (q == NULL)
		return -1;
	q->data = *data;
	q->next = p->next;
	p->next = q;
	return 0;
}

(3)删除
在这里插入图片描述
关键代码 p->next = p->next->next;q = p->next; p->next = q->next; 最好用第二种方式,不然删除的结点的内存无法释放。

int list_delete(struct node *list, datatype *data)
{
	struct node *p = list, *q;
	
	while (p->next && p->next->data != *data)
		p = p->next;
	if (p->next == NULL)
		return -1;
	q = p->next;
	p->next = q->next;
	free(q);
    return 0;
}

int list_delete_at(struct node *list, int i, datatype *data)
{
	int j = 0;
	struct node *p=list, *q;
	p = list; //头指针
	j = 1;

	while (p->next && j < i) {  //遍历寻找第i-1的元素,即要删除结点的前一个结点
		p = p->next;
		j++;
	}

	if ( !(p->next) || j>i ) 
		return -1;			// 第i个元素不存在
	//p->next = p->next->next;
	q = p->next;
	p->next = q->next;		// 将q的后继赋值给p的后继
	*data= q->data;		// 将q结点中的数据给e
	free(q);				// 释放内存
	return 0;
}

(4)查找

int list_find(struct node *list, int i, datatype *data)
{
	int j = 1; //j为计数器
	struct node *p;  //声明一结点指针p
	
	p = list->next;  // 让p指向链表的第一个结点
	
	while (p && j<i) {  //p 不为空或者计数器j还没有等于i时,循环继续
		p = p->next; //让p指向下一个结点 
		++j;
	}
	if (p==NULL || j>i)
		return -1;   //第i元素不存在
	*data= p->data;  //取第i个元素的数据
	
	return 0;
}	

(5)整表创建

  • 头插法
    在这里插入图片描述

void create_list_head(struct node **head, int n)
{
	struct node *p;
	int i;

	*head= (struct node *)malloc(sizeof(struct node));
	(*head)->next = NULL;	//先建立一个带头结点的单链表
	for (i = 0; i<n; i++) {
		p = (struct node *)malloc(sizeof(struct node));//生成新结点
		p->data = i + 1;
		p->next = (*head)->next;
		(*head)->next = p;     //插入到表头
	}
}
  • 尾插法
    在这里插入图片描述

void create_list_tail(struct node **head, int n)
{
	struct node *p, *r;
	int i;

	*head = (struct node *)malloc(sizeof(struct node));
	r = *head;
	
	for (i = 0; i<n; i++) {
		p = (struct node *)malloc(sizeof(struct node));  //生成新结点
		p->data = i+1;   
		r->next = p; //将表尾终端结点的指针指向新结点
		r = p; //将当前的新结点定义为表尾终端结点
	}
	r->next = NULL;
}

(6)销毁

void list_destroy(struct node *list)
{
	struct node *cur, *next;
	
	for (cur=list->next; cur != NULL; cur = next) {
		next= cur->next;
		free(cur);
	}
	free(list);
}

(7)其他

//判断链表是否为空
int list_isempty(struct node *list)
{
	if (list->next == NULL)
		return -1;
	return 0;
}

//显示
void list_show(struct node * list)
{
    struct node *cur;

	if (list_isempty(list) != 0)
		return -1;
	for (cur=list->next; cur!=NULL; cur=cur->next){
        printf("%d ",cur->data);
    }
    printf("\n");
}

(8) 示例

#include <stdio.h>
#include <stdlib.h>

int main() 
{ 
	struct node *list=NULL;
    int i, ret;

    list = list_create();
    if (list == NULL) {
        printf("list create failed... \n");
        exit(1);
    }

    for (i = 0; i < 5; i++) {
        ret = list_insert_at(list, i, i+1);
        if (ret != 0) {
            printf("list insert failed... %d\n",ret);
            break;
        }
    }
    list_show(list);

    datatype data = 5;
    list_delete(list, &data);

    list_show(list);

    list_destroy(list);
}

在这里插入图片描述

2.2 无头结点的实现方式

在这里插入图片描述

  • 无头结点不需要创建链表

// 插入,头插法

// list只是个头指针的地址,回传链表,不是头结点
int list_insert(struct node **list, elemtype elem)
{
	struct node *new;
	
	new = (struct node *)malloc(sizeof(struct node));
	if (new == NULL)
		return -1;
	
	new->data = elem; 
	new->next = *list;  //将p的后继结点赋值给s的后继
	*list = new;		// 将s赋值给p的后继
	
	return 0;
}

//显示
void list_show(struct node *list)
{
	struct node *cur;

	for (cur = list; cur != NULL; cur = cur->next) {
		printf("%d", cur->data);
	}
}

// 删除,首部删除
int list_delete(struct node **list)
{
	struct node *cur;
	
	if (*list == NULL)
		return -1;
		
	cur = *list;
	*list = (*list)->next;
	free(cur);
	return 0;
}

// 查找
struct node *list_find(struct node *list, elemtype e)
{
	struct node *cur;

	for (cur = list; cur != NULL; cur = cur->next) {
		if (cur->data == e) {
			return cur; //返回当前链表结点
		}
	}
	return NULL;
}

//销毁
void list_destroy(struct node *list)
{
	struct node *cur;
	
	if (list == NULL)
		return;
	for (cur = list; cur != NULL; cur = list) {
		list = cur->next;
		free(cur);
	}
}
2.3 小结
  • 单向链表是后续链表的基础
  • 单向链表随机访问比较差

三、单向循环链表

  • 单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成个环,这种头尾相接的单链表称为单循环链表,简称循环链表( circular linked list )
    在这里插入图片描述
  • 循环链表和单链表的主要差异就在于循环的判断条件上,单链表判断循环结束为:node->next==NULL;而循环链表判断循环结束为:(node->next)等于头结点。
  • 终端结点用尾指针rear指示,则查找终端结点是O(1),而开始结点,其实就是rear->next>next,其时间复杂也为O(1)。
    在这里插入图片描述
    在这里插入图片描述

// 创建循环链表, 不带头结点
struct node *create_list_head( int n)
{
	struct node *list, *new, *cur;
	int i = 1;

	list = (struct node *)malloc(sizeof(struct node));
	if (list == NULL)
		return NULL;
	//循环链表与单链表创建的唯一区别就是下面这行代码
	//list->next = NULL;	//先建立一个带头结点的单链表
	list->next = list; //先建立一个带头结点的循环链表
	list->data = i;
	cur = list;
	i++;
	for (; i<n; i++) {
		new= (struct node *)malloc(sizeof(struct node));//生成新结点
		if (new == NULL)
			return NULL;
		new ->data = i;
		new ->next = list;
		cur->next = new;     //插入到表头
		cur = new;
	}
	return list;
}


//遍历链表
void get_elem(struct node *head, int index, datatype *e){
    int i=1;
    //因为头节点无数据所以p指向l->next
    struct node *p=head->next;
    //因为是循环链表,p没有为空的时候,所以判断条件应为p不指向头节点的时候
    while ((p!=(head))&&i<index) {
        p=p->next;
        i++;
    }
    *e=p->data;
}
//插入
void insert_elem(struct node *head,int index, datatype e){
    int i=1;
    struct node *p=head->next;
    while(p!=head && i<index){
        p=p->next;
        i++;
    }
    struct node *s;
    s=(struct node *)malloc(sizeof(struct node));
    s->data=e;
    s->next=p->next;
    p->next=s;
}
//删除
void list_delete(struct node **list, int n)
{
    int i=1;
    struct node *cur=*list, *node;
    //因为是循环链表,p没有为空的时候,所以判断条件应为p的next不指向头节点的时候
    while (cur != cur->next) {
	    while(i<n){
	    	node = cur;
	        cur=cur->next;
	        i++;
	    }
	    printf("%d ", cur->data);
	    node->next = cur->next;
	    free(cur);
	    cur = node->next;
	    i = 1;
    }
    *list = cur;
    printf("\n");
}
//清表
void clear_list(struct node *head){
    struct node *q, *p=(head)->next;
    //因为是循环链表,p没有为空的时候,所以判断条件应为p不指向头节点的时候
    while(p != head){
        q=p->next;
        free(p);
        p=q;
    }
    head->next=head;
}
void list_show(struct node *list)
{
    struct node *p;
	
	for (p = list; p->next != list; list = list->next)
        printf("%d ",p->data);
    printf("%d \n",p->data);
}

四、双向链表

  • 在单链表的每个结点中,再设置一个指向其前驱结点的指针域,则为 双向链表( double linked list)。
  • 所以在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。
  • 双向链表存储结构
typedef int  datatype ;    //元素数据域类型可为任一类型,不局限于int
struct node
{
	datatype data;
	struct node *prev, *next;
};
  • 双向链表的插入
    在这里插入图片描述
// 头插法
s->prev= p;   // 把p赋值给s的前驱,如图中①
s->next = p->next;  // 把p->next 赋值给s的后继,如图中②
p->next->prev= s; //把要赋值给p->next的前驱,如图中③
p->next = s; //把要s赋值给p的后继,如图中④

// 尾插法
s->prev = p->prev;
s->next = p;
p->prev->next = s;
p->prev = s;
  • 双向链表的删除
    在这里插入图片描述
p->prev->next = p->next;  //把p->next赋值给p->prior的后继,如图中①
p->next->prev= p->prev; //把p->prior赋值给p->next的前驱,如图中②
free(p);   //释放结点

五、总结

在这里插入图片描述
总结1:若线性表需要频繁查找,很少进行插入和删除操作时,宜采用顺序存储结构。若需要频繁插入和删除时,宜采用单链表结构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值