数据结构—线性表

一、线性表介绍
1、线性结构

在数据元素存在非空有限集中:

  • 存在唯一的一个被称为“第一个”的数据元素

  • 存在唯一的一个被称为“最后一个”的数据元素

  • 除了第一个外,集合中每个数据元素都只有一个前趋元素

  • 除了最后一个外,集合中每个数据元素都只有一个后继元素

2、线性表

线性表是一个有n个数据元素的有限序列,同一个线性表中的元素必定有相同特性,元素之间存在序偶关系

线性表中的元素个数n(n>=0)定义为该表的长度,当n==0时称为空表,非空表中每个数据元素都有一个确定的位置(下标)

线性表是一个相当灵活的数据结构,它的长度可以根据需要增长或缩短

二、线性表的顺序的表示和存储:

线性表的存储使用一组连续内存来依次存储线性表中的数据元素。

注意:1、要时刻保持元素之间的连续性、2、千万不要越界

优点:1、支持随机访问 2、查找、修改、排序效率比较高 3、大块的连续内存不容易产生内存碎片

缺点:1、对元素插入、删除时效率很低 2、大块内存对内存要求较高

三、线性表的链式表示和存储:

链式存储结构不要求内存位置物理上是连续的,因此元素可以存储在内存的任何位置(可以是连续的,也可以不连续)

元素a[i]和a[i+1]之间的逻辑关系不是依靠相互位置,而是在元素中增加一个一个指向其后继元素的数据项(元素指针),从而表示相互之间的逻辑关系,元素本身的数据+后继元素的地址 组成了存储映像,俗称 节点(node)

typedef struct Node
{
    TYPE data;          //  数据域  
    struct Node* next;  //  指针域
}Node;

若干个节点通过指针域依次连接起来,形成的线性表结构称为链式表,简称链表,如果指针域中只有一个指向下一个节点的指针,这种链表称为单向链表。

单向链表

单向链表中必须有一个指向第一个节点的指针,该指针称为头指针,被它指向的节点称为头节点

头节点可以存储、也可以不存储有效数据,如果不存储有效数据的话,那么头节点只是单纯地作为一个占位节点存在

最后一个节点称为尾节点,尾节点的next指向空(NULL),作为结束标志

1、不带头节点的单向链表

定义:第一个节点中的数据域存储有效数据。

注意:当需要对单链表的头指针发生修改时,例如头添加、头删除、插入等操作,参数需要传递头指针的地址(二级指针),处理相对麻烦

注意:当进行删除时,需要获取到待删除节点的前趋节点,但是如果删除的位置刚好是第一个节点,它没有前趋节点,所以需要额外判断处理

2、带头节点的单向链表

定义:第一个节点中的数据域不存储有效数据,该头节点只是用于指向第一个有效数据的节点而存在。

所以,由于头节点不会因为添加、插入、删除该改变,所以不需要传递二级指针

typedef struct List
{
    ListNode* head;     //  永远指向头节点 必须要有
    ListNode* tail;     //  尾指针,可以有 也可以没有
    size_t size;        //  数量 可以有 也可以没有
}List;

注意:尾指针tail,能直接找到最后一个节点,但是在尾删除操作时,发挥不了作用,因为要找尾节点的前趋。

常考笔试题:
1、单链表逆序,要在原基础上进行逆序
2、找到链表的倒数第n个节点
3、判断链表中是否存在环
4、找出环形链表的入口节点
5、合并两个有序链表,合并后保持有序性
6、判断两个链表是否构成Y型链表,如果是,找出第一个公共节点

四、静态链表

  • 静态链表的节点存储在一段连续内存中,通过节点中称为游标的一个正整数来访问后继节点

typedef StaticNode
{
    TYPE data;      //  数据域
    int index;  //  游标
}StaticNode;
​
int main()
{
    StaticNode arr[100] = {};
    
    arr[0].data = 1;
    arr[0].index = 5;
    
    arr[5].index = 3;
    arr[3].index = 8;
    arr[8].index = -1;
}
  • 在静态链表中进行插入、删除时,只需要修改游标的值即可不需要拷贝内存,也能达到链表的效果

  • 但是也牺牲了随机访问节点的功能,而且链表的优点也有缺失。

  • 是给没有指针的编程语言提供一种操作单链表的方式。

五、循环链表

  • 循环链表的最后一个节点的next不再指向NULL,而是指向头节点。如果是单链表就称为单循环链表

  • 好处是能够通过任意节点可以遍历整个链表

六、双向链表(双向循环链表)

  • 所谓的双向链表就是链表节点中有两个指针域,一个指向前一个节点,叫做前趋指针(prev),另一个指向后一个节点,称为后继指针(next)

  • 因此可以从后往前遍历链表,对于要访问链表后半部的节点的操作效率更高

//  双向链表的节点结构
typedef struct ListNode
{
    struct ListNode* prev;      //  前趋
    TYPE data;
    struct ListNode* next;      //   后继
}ListNode;
  • 在双向链表的基础上,让最后一个节点的next指向头节点,让头节点的prev指向最后一个节点,构成了双向循环链表

七、Linux内核链表

  • 在普通的链表中,目前面临无法做到任何类型的数据都可以存储的问题。

  • Linux内核链表是一种通用的双向循环链表,面对通用的问题,Linux内核链表的节点干脆不存储任何数据域,只有指针域,节点只负责串联起每个节点,不负责存储数据。

  • 如果要使用Linux内核链表时,把节点放入到数据中。

目的:根据结构体中某个成员的地址,能够计算出所在结构体的首地址,从而用户在设计Linux内核链表的节点时,不需要一定放在在数据的首位成员,增加可用性
//  计算出结构体type的成员mem所在的地址距离第一个成员位置的字节数           
#define offsetof(type,mem) \
    ((unsigned long)(&(((type*)0)->mem)))
​
​
//  根据结构成员mem的地址(ptr) 计算出它所在结构(type)变量的首地址
//  ptr要计算的某个节点的地址  type是ptr所在结构体名 mem是它的结构成员名
#define node_to_obj(ptr,type,mem) \
        ((type*)((char*)ptr - offsetof(type,mem)))
​

八、通用链表

Linux内核链表虽然设计很巧妙,但是不利于初学者使用,另一种通用的设计思路是借助void*的兼容性,来设计一种链表,称为通用链表,这种链表还需要借助回调函数。

以下是通用链表的例子:

list.c

#include <stdlib.h>

#include "list.h"

//创建节点

static ListNode* create_node(void *ptr)
{
	ListNode* node=malloc(sizeof(ListNode));
	node->ptr=ptr;
	node->prev=node;
	node->next=node;
	return node;
}

//创建链表
List* create_list(void)
{
	List *list=malloc(sizeof(List));
	list->head=create_node(NULL);
	list->size=0;
	return list;
}

//在两个节点之间插入新节点  依赖函数
static void _add_list(ListNode* p,ListNode* n,void *ptr)
{
	ListNode* node=create_node(ptr);
	p->next=node;
	n->prev=node;
	node->next=n;
	node->prev=p;
}
//删除节点 依赖函数
static void _del_list(ListNode* node)
{
	node->prev->next=node->next;
	node->next->prev=node->prev;
//	void* ptr=node->ptr;	
	free(node->ptr);
	free(node);
//	return ptr;
}

//根据位置查找节点  依赖函数
static ListNode* _index_list(List *list,int index)
{
	if(index<0||index>=list->size)
		return NULL;
	if(index<list->size/2)
	{
		ListNode* node=list->head->next;
		while(index--)
			node=node->next;
		return node;
	}
	else
	{
		ListNode* node=list->head->prev;
		while(++index<list->size)
			node=node->prev;
		return node;
	}
}

//在末尾追加
void add_list(List* list,void * ptr)
{
	_add_list(list->head->prev,list->head,ptr);
	list->size++;
}

//插入
bool insert_list(List *list,int index,void *ptr)
{
	ListNode *node=_index_list(list,index);
	if(node==NULL)
		return false;
	_add_list(node->prev,node,ptr);
	list->size++;
	return true;
}

//按位置删除
bool del_index_list(List *list,int index)
{
	ListNode *node=_index_list(list,index);
	if(node==NULL)
		return false;
	_del_list(node);
	list->size--;
	return true;
}

//按值删除 *
bool del_value_list(List *list,void *ptr,fp cmp)
{
	if(list->size==0)
		return false;
	for(ListNode* n=list->head->next;n!=list->head;n=n->next)
	{
		if(0==cmp(n->ptr,ptr))
		{
			_del_list(n);
			list->size--;
			return true;
		}
	}
	return false;
}

//查询
void *query_list(List *list,void *ptr,fp cmp)
{
	if(list->size==0)
		return NULL;
	for(ListNode* n=list->head->next;n!=list->head;n=n->next)
	{
		if(0==cmp(n->ptr,ptr))
		{
			return n->ptr;
		}
	}
	return NULL;
}


//访问
void *access_list(List* list,int index)
{
	ListNode *node=_index_list(list,index);
	if(node=NULL)
		return NULL;
	return node->ptr;
}


//排序
void sort_list(List *list,fp cmp)
{
	for(ListNode* i=list->head->next;i!=list->head->prev;i=i->next)
	{
		ListNode* min=i;
		for(ListNode* j=i;j!=list->head;j=j->next)	
		{
			if(cmp(j->ptr,min->ptr)<0)
			{
				min=j;
			}
		}
		if(min!=i)
		{
			//不需要交换内存,只需要交换指针指向
			void *tmp=i->ptr;
			i->ptr=min->ptr;
			min->ptr=tmp;	
		}
	}
}

//清空
void clear_list(List *list)
{
	while(list->size)
		del_index_list(list,0);

}

//销毁
void destroy_list(List *list)
{
	clear_list(list);
	free(list->head);
	free(list);
}


//遍历
void show_list(List *list,void (*show)(void*))
{
	for(ListNode* n=list->head->next;list->head!=n;n=n->next)
	{
		show(n->ptr);
	}
}

list.h

#ifndef LIST_H
#define LIST_H

#include <stdio.h>
#include <stdbool.h>
//通用链表节点
typedef struct ListNode
{
	void* ptr;
	
	struct ListNode *prev;
	struct ListNode *next;
}ListNode;
//通用链表结构
typedef struct List
{
	ListNode* head;
	size_t size;
}List;

//创建链表
List* create_list(void);
//追加
void add_list(List* list,void * ptr);
//插入
bool insert_list(List *list,int index,void *ptr);
//按位置删除
bool del_index_list(List *list,int index);
typedef int INT;
typedef int (*fp)(const void* ,const void*);  
//按值删除 *
bool del_value_list(List *list,void *ptr,fp cmp);
//bool del_value_list(List *list,bool (*value)(void*));
//查询?
void *query_list(List *list,void *ptr,fp cmp);
//访问
void *access_list(List* list,int index);
//排序?
void sort_list(List *list,fp cmp);
//清空
void clear_list(List *list);
//销毁
void destroy_list(List *list);
//遍历
void show_list(List *list,void (*show)(void*));

#endif//LIST_H

  • 27
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值