数据机构 线性表

线性结构

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

  • 存在唯一的一个被称作“第一个”的数据元素
  • 存在唯一的一个被称作“最后一个”的数据元素
  • 除第一个之外,集合中的每个数据元素均只有一个前驱
  • 除最后一个之外,集合中的每个数据元素均只有一个后继

线性表

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

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

线性表的顺序表示

特点

逻辑关系上相邻的两个元素在物理位置上也相邻

优点

  • 逻辑关系上两个相邻的两个元素在物理位置上也相邻,因此可以随机存取表中的任意元素

  • 它的存储位可以用一个简单直观的公式来表示。

缺点

插入删除操作时,需要移动大量的元素

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define TYPE int

// 设计数据结构
typedef struct Array
{
	TYPE* base;	// 存储元素的基址
	size_t cnt; // 元素的数量
	size_t cap; // 表的容量
}Array;

// 创建线性表,调用者需要提供容量参数,成功返回线性表指针
Array* creat_array(size_t cap)
{
	// 为线性表创建内存空间
	Array* array = malloc(sizeof(Array));
	// 创建存储元素的内存空间
	array->base = malloc(sizeof(TYPE)*cap);
	// 初始化数量成员
	array->cnt = 0;
	// 备份线性表的容量
	array->cap = cap;
	// 返回线性表指针
	return array;
}

// 销毁线性表,调用者只需要提供线性表指针即可
void destory_array(Array* array)
{
	// 释放存储元素的内存空间
	free(array->base);
	// 释放线性表内存空间
	free(array);
}

// 清理所有元素
void clear_array(Array* array)
{
	array->cnt = 0;
}

// 判断线性表是否是空表
bool empty_array(Array* array)
{
	return 0 == array->cnt;
}

// 求线性表的长度
size_t length_array(Array* array)
{
	return array->cnt;
}

// 访问指定位置的元素
bool get_array(Array* array,int index,TYPE* elemp)
{
	if(index >= array->cnt)
		return false;

	*elemp = array->base[index];
	return true;
}

// 在末尾添加元素
void add_back_array(Array* array,TYPE elem)
{
	// 容量如果不够用
	if(array->cnt >= array->cap)
	{
		// 容量扩展两倍
		array->cap *= 2;
		// 元素存储空间扩展两倍
		array->base = realloc(array->base,sizeof(TYPE)*array->cap);
	}

	// 末尾添加元素
	array->base[array->cnt++] = elem;
}

// 插入元素
bool insert_array(Array* array,int index,TYPE elem)
{
	// 如果下标不合法则插入失败
	if(index >= array->cnt)
		return false;

	// 把最后一个元素添加到末尾
	add_back_array(array,array->base[array->cnt-1]);

	// 线性表的顺序存储才可以使用
	memmove(array->base+index+1,array->base+index,sizeof(TYPE)*(array->cnt-index-2));
	
	array->base[index] = elem;
	return true;
}

// 删除元素,按位置删除
bool delete_index_array(Array* array,int index)
{
	if(index >= array->cnt)
		return false;

	memmove(array->base+index,array->base+index+1,sizeof(TYPE)*(array->cnt-index-1));
	array->cnt--;
	return true;
}

// 查询元素
int query_array(Array* array,TYPE elem,int (*compare)(const void*,const void*))
{
	for(int i=array->cnt-1; i>=0; i--)
	{
		if(!compare(&elem,array->base+i))
			return i;
	}
	return -1;
}

// 删除元素,按值删除
bool delete_value_array(Array* array,TYPE elem,int (*compare)(const void*,const void*))
{
	return delete_index_array(array,query_array(array,elem,compare));
}

// 对线性表进行排序
void sort_array(Array* array,int (*compare)(const void*,const void*))
{
	bool flag = true;
	for(int i=array->cnt-1; flag && i>0; i--)
	{
		flag = false;
		for(int j=0; j<i; j++)
		{
			if(1 == compare(array->base+j,array->base+j+1))
			{
				TYPE tmp = array->base[j];
				array->base[j] = array->base[j+1];
				array->base[j+1] = tmp;
				flag = true;
			}
		}
	}
}

// 遍历线性表,只是为了测试
void show_array(Array* array)
{
	for(int i=0; i<array->cnt; i++)
	{
		printf("%d ",array->base[i]);
	}
	printf("\n");
}

int main(int argc,const char* argv[])
{
	Array* array = creat_array(10);
	for(int i=0; i<10; i++)
	{
		add_back_array(array,rand()%100);
	}
	
	show_array(array);

	int intcmp(const void* p1,const void* p2)
	{
		if(*(int*)p1 > *(int*)p2)
			return 1;
		if(*(int*)p1 < *(int*)p2)
			return -1;
		return 0;

	}
    
	delete_value_array(array,77,intcmp);

	sort_array(array,intcmp);
	
	show_array(array);
	destory_array(array);
	return 0;
}

**作业:**把顺序线性表的成员指针实现方式改为柔性数组

线性表的链式存储结构

特点

元素可以使用存储内存中的任何位置(可以是连续的,也可以不连续)

元素a[i]和a[i+1]的逻辑关系不依靠相对位置,而是元素中增加一个指示其后续的数据,元素本身的数据增加一个指示其后续元素的数据(元素指针)

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

**链式表(简称链表):**若干个元素节点通过指针域连接起来,形成的线表结构

单向链表:该链表节点中只有一个指向后继元素的指针域 必须有一个指向第一节的指针,被称为头指针,被他指向的节点被称为头结点。最后一个节点指向空(NULL)

节点(node):元素本身的数据+后继信息构成了存储映像

头节点:指向存储结构的第一个结构体,可以不存储信息

优点

  • 不要求逻辑相邻元素在物理位置上也相邻,元素可以使用存储内存中的任意位置(可以是连续,也可以不连续)
  • 在插入,删除操作时,不需要移动大量的数据

缺点

  • 不能随机存取

代码

不带头结点的单项链表
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define TYPE int

typedef struct Node
{
	TYPE data;
	struct Node* next;
}Node;

// 创建节点
Node* create_node(TYPE data)
{
	// 创建节点内存
	Node* node = malloc(sizeof(Node));
	// 赋值数据域
	node->data = data;
	// 初始化指针域
	node->next = NULL;
	return node;
}

// 头添加元素
void front_list(Node** head,TYPE data)
{
	// 创建节点
	Node* node = create_node(data);
	node->next = *head;
	*head = node;
}

// 删除元素
bool delete_index_list(Node** head,int index)
{
	// 删除每个节点,因为第一个节点没有前驱
	if(0 == index)
	{
		Node* node = *head;
		*head = (*head)->next;
		free(node);
		return true;
	}

	// 找到要删除的节点的前驱
	Node* prev = *head;
	while(NULL!=prev->next && index-->1)
			prev = prev->next;

	if(NULL != prev->next)
	{
		// 备份要删除的节点
		Node* node = prev->next;
		// 前驱节点的指针域指向后继节点
		prev->next = prev->next->next;
		free(node);
		return true;
	}

	return false;
}

// 插入元素
bool insert_list(Node** head,int index,TYPE data)
{
	Node* node = create_node(data);
	if(0 == index)
	{
		node->next = *head;
		*head = node;
		return true;
	}

	Node* prev = *head;
	while(NULL!=prev->next && index-->1)
			prev = prev->next;

	if(NULL != prev->next)
	{
		node->next = prev->next;
		prev->next = node;
		return true;
	}

	return false;
}

// 遍历链表
void show_list(Node* head)
{
	for(Node* n=head; NULL!=n; n=n->next)
	{
		printf("%d ",n->data);
	}
	printf("\n");
}

int main(int argc,const char* argv[])
{
	// 创建头指针
	Node* head = NULL;
	for(int i=0; i<10; i++)
	{
		front_list(&head,i);
	}
	show_list(head);
	delete_index_list(&head,9);
	insert_list(&head,9,100);
	show_list(head);
	return 0;
}
带头节点的单项链表

在执行链表的插入、删除操作时,需要被操作节点的前驱节点和后继节点

如果被操作节点是头节点,则它没有前驱节点,需要额外特殊处理

因此为了方便插入和删除操作所以给单链表增加一个空的头节点

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

#define TYPE int

typedef struct Node
{
	TYPE data;
	struct Node* next;
}Node;

// 创建节点
Node* create_node(TYPE data)
{
	Node* node = malloc(sizeof(Node));
	node->data = data;
	node->next = NULL;
	return node;
}


// 根据前驱节点删除元素
static bool _delete_list(Node* prev)
{
	if(NULL == prev || NULL == prev->next)
		return false;

	Node* node = prev->next;
	prev->next = node->next;
	free(node);
	return true;
}

static bool _insert_list(Node* prev,TYPE data)
{
	if(NULL == prev)
		return false;
		
	Node* node = create_node(data);
	node->next = prev->next;
	prev->next = node;
	return true;
}

// 返回index下标的前驱节点
static Node* _index_list(Node* head,int index)
{
	// 找下标为i的节点的前驱
	Node* prev = head;
	while(NULL != prev->next && index-- >= 1)
		prev = prev->next;

	// index 非法,超出了节点的数量
	if(NULL == prev->next || index < -1)
		return NULL;

	return prev;
}

// 查询出值为data的前驱节点
static Node* _query_list(Node* head,TYPE data)
{
	Node* prev = head;
	while(NULL != prev->next && data != prev->next->data)
		prev = prev->next;

	if(NULL == prev->next)
		return NULL;

	return prev;
}

// 创建带头的空链表
Node* create_list(void)
{
	Node* node = malloc(sizeof(Node));
	node->next = NULL;
	return node;
}

// 销毁链表
void destory_list(Node* head)
{
	while(NULL != head)
	{
		Node* node = head;
		head = head->next;
		free(node);
	}
}

// 在头部添加元素
void front_list(Node* head,TYPE data)
{
	_insert_list(head,data);
}

// 在指定位置插入元素
bool insert_list(Node* head,int index,TYPE data)
{
	/*
	// 找到要插入位置节点的前驱
	Node* prev = head;
	while(NULL != prev->next && index-- >= 1)
		prev = prev->next;

	// index 非法,超出了节点的数量
	if(NULL == prev->next || index < -1)
		return false;
	

	Node* prev = _index_list(head,index);
	if(NULL == prev)
		return false;

	Node* node = create_node(data);
	node->next = prev->next;
	prev->next = node;
	return true;
	*/
	
	return _insert_list(_index_list(head,index),data);
}

// 删除指定位置的元素
bool delete_index_list(Node* head,int index)
{
	/*
	// 找到要删除的节点的前驱
	Node* prev = head;
	while(NULL != prev->next && index-- >= 1)
		prev = prev->next;

	// index 非法,超出了节点的数量
	if(NULL == prev->next || index < -1)
		return false;
	Node* prev = _index_list(head,index);
	if(NULL == prev)
		return false;

	// 备份要删除的节点
	Node* node = prev->next;
	// 前驱节点接后继节点
	prev->next = prev->next->next;
	// 删除节点
	free(node);
	return true;
	*/
	return _delete_list(_index_list(head,index));
}

// 按值删除元素
bool delete_value_list(Node* head,TYPE data)
{
	/*
	Node* prev = _query_list(head,data);
	if(NULL == prev)
		return false;

	Node* node = create_node(data);
	node->next = prev->next;
	prev->next = node;
	return true;
	*/
	return _delete_list(_query_list(head,data));
}

// 按值修改
bool modify_value_list(Node* head,TYPE old,TYPE new)
{
	Node* prev = _query_list(head,old);
	if(NULL == prev)
		return false;

	prev->next->data = new;
	return true;
}

// 按位置修改
bool modify_index_list(Node* head,int index,TYPE new)
{
	Node* prev = _index_list(head,index);
	if(NULL == prev)
		return false;

	prev->next->data = new;
	return true;
}

// 访问指定位置的元素
bool get_list(Node* head,int index,TYPE* data)
{
	Node* prev = _index_list(head,index);
	if(NULL == prev)
		return false;

	*data = prev->next->data;
	return true;
}

// 查询元素
int query_list(Node* head,TYPE key)
{
	int index = 0;
	for(Node* n=head->next; NULL!=n; n=n->next)
	{
		if(n->data == key)
			return index;
		index++;
	}
	
	return -1;
}

// 经典排序
void sort_list(Node* head)
{
	for(Node* i=head->next; NULL!=i->next; i=i->next)
	{
		for(Node* j=i->next; NULL!=j; j=j->next)
		{
			if(i->data > j->data)
			{
				TYPE tmp = i->data;
				i->data = j->data;
				j->data = tmp;
			}
		}
	}
}

void show_list(Node* head)
{
	// 要跳过头节点
	for(Node* n=head->next; NULL!=n; n=n->next)
	{
		printf("%d ",n->data);
	}
	printf("\n");
}

int main(int argc,const char* argv[])
{
	Node* list = create_list();
	for(int i=0; i<10; i++)
	{
		front_list(list,rand()%100);
	}
	show_list(list);
	//delete_index_list(list,-1);
	//delete_value_list(list,9);
	insert_list(list,9,100);
	sort_list(list);
	show_list(list);
	return 0;
}

笔试题
  • 反转链表

    void reverse_list(Node* head)
    {
        Node *n1 = NULL , *n2 = head->next;
        while(NULL != n2)
        {
            Node* n3 = n2->next;
            n2->next = n1;
            n1 = n2;
            n2 = n3;
        }
     
        head->next = n1;
    }
    
  • 找到环形链表的入口

    Node* in_ring_list(Node* head)
    {
        Node* n1 = head->next;
        Node* n2 = head->next;
    
        while(NULL != n2 && NULL != n2->next)
        {
            n1 = n1->next;
            n2 = n2->next->next;
            if(n1 == n2)
                break;
        }
    
        if(NULL == n2 || NULL == n2->next)
            return NULL;
    
        n1 = head->next;
        while(n1 != n2)
        {
            n1 = n1->next;
            n2 = n2->next;
        }
    
        return n1;
    }
    
  • 判断两个链表的Y型入口

    Node* in_y_list(Node* head1,Node* head2)
    {
    	Node *n1 = head1->next , *n2 = head2->next;
        while(n1 != n2)
        {
            n1 = NULL==n1 ? head2->next : n1->next;
            n2 = NULL==n2 ? head1->next : n2->next;
        }
    
        return n1;
    }
    
  • 访问链表的倒数第K个节点

    Node* get_tail_k(Node* head,int k)
    {
        Node* n1 = head->next;
        for(int i=0; i<k; i++)
        {
            if(NULL == n1)
                return NULL;
            n1 = n1->next;
        }
    
        Node* n2 = head->next;
        while(NULL!=n1 && NULL!=n2)
        {
            n1 = n1->next;
            n2 = n2->next;
        }
        return n2;
    }
    
双向链表

链表节点中有两个指针域

  • 前驱指针 prev
  • 后继指针 next

优点:可以从后向前遍历链表,对于链表的后半部分节点的访问效率大大提升

从前往后操作时,需要一个空的尾结点

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

#define TYPE int

// 双向链表节点
typedef struct Node
{
	struct Node* prev;	// 前驱指针域
	TYPE data;			// 数据域
	struct Node* next;	// 后继指针域
}Node;

// 创建节点
Node* create_node(TYPE data)
{
	Node* node = malloc(sizeof(Node));
	node->data = data;
	node->prev = NULL;
	node->next = NULL;
	return node;
}

typedef struct DoubleList
{
	Node* head;
	Node* tail;
	size_t size;
}DoubleList;

void _insert_list(Node* prev,Node* next,TYPE data)
{
	Node* node = create_node(data);
	node->next = next;
	node->prev = prev;
	prev->next = node;
	next->prev = node;
}

// 创建链表
DoubleList* create_list(void)
{
	DoubleList* list = malloc(sizeof(DoubleList));
	list->head = malloc(sizeof(Node));
	list->tail = malloc(sizeof(Node));
	list->head->prev = list->tail->next = NULL;
	list->head->next = list->tail;
	list->tail->prev = list->head;
	list->size = 0;
	return list;
}

// 销毁链表
void destroy_list(DoubleList* list)
{
	Node* node = list->tail;
	while(NULL!=node)
	{
		Node* temp = node;
		node = node->prev;
		free(temp);
	}
}

// 头添加
void add_head_list(DoubleList* list,TYPE data)
{
	_insert_list(list->head,list->head->next,data);
	list->size++;
}

// 尾添加
void add_tail_list(DoubleList* list,TYPE data)
{
	_insert_list(list->tail->prev,list->tail,data);
	list->size++;
}

// 遍历
void show_list(DoubleList* list)
{
	for(Node* n=list->head->next; n!=list->tail; n=n->next)
	{
		printf("%d ",n->data);
	}
	printf("\n");
	for(Node* n=list->tail->prev; n!=list->head; n=n->prev)
	{
		printf("%d ",n->data);
	}
	printf("\n");
}

// 判断链表是否是空表
bool empty_list(DoubleList* list)
{
	return 0 == list->size;
}

void _del_list(Node* node)
{
	Node* prev = node->prev;
	Node* next = node->next;
	prev->next = next;
	next->prev = prev;
	free(node);
}

// 头删除
bool del_head_list(DoubleList* list)
{
	if(empty_list(list))
		return false;

	_del_list(list->head->next);
	list->size--;
	return true;
}

// 尾删除
bool del_tail_list(DoubleList* list)
{
	if(empty_list(list))
		return false;
	
	_del_list(list->tail->prev);
	list->size--;
	return true;
}

Node* _index_list(DoubleList* list,int index)
{
	if(index >= list->size)
		return NULL;
	
	if(index < list->size/2)
	{
		Node* node = list->head->next;
		while(index--)
			node = node->next;
		return node;
	}
	else
	{
		Node* node = list->tail->prev;
		while(list->size > ++index)
			node = node->prev;
		return node;
	}
}

// 指定位置插入
bool insert_index_list(DoubleList* list,int index,TYPE data)
{
	Node* node = _index_list(list,index);
	if(NULL == node)
		return false;

	_insert_list(node->prev,node,data);
	list->size++;
	return true;
}

// 指定位置删除
bool del_index_list(DoubleList* list,int index)
{
	Node* node = _index_list(list,index);
	if(NULL == node)
		return false;

	_del_list(node);
	list->size--;
	return true;

}

// 查询
Node* query_list(DoubleList* list,TYPE data)
{
	Node* left = list->head , *right = list->tail;
	do{
		left = left->next;
		right = right->prev;
		if(data == left->data)
			return left;
		if(data == right->data)
			return right;
	}while(left != right && left->next != right);
	return NULL;
}

// 指定值删除
bool del_value_list(DoubleList* list,TYPE data)
{
	Node* node = query_list(list,data);
	if(NULL == node)
		return false;
	
	_del_list(node);
	list->size--;
	return true;
}

// 修改指定位置的值
bool modify_index_list(DoubleList* list,int index,TYPE data)
{
	Node* node = _index_list(list,index);
	if(NULL == node)
		return false;

	node->data = data;
	return true;
}

// 修改指定值的值
bool modify_value_list(DoubleList* list,TYPE old,TYPE data)
{
	Node* node = query_list(list,old);
	if(NULL == node)
		return false;
	
	node->data = data;
	return true;
}


// 排序
void sort_list(DoubleList* list)
{
	/* 
	// 冒泡排序
	bool flag = true;
	for(Node* r=list->tail->prev; flag && r!=list->head->next; r=r->prev)
	{
		flag = false;
		for(Node* l=list->head->next; l!=r; l=l->next)
		{
			if(l->data > l->next->data)
			{
				TYPE temp = l->data;
				l->data = l->next->data;
				l->next->data = temp;
				flag = true;
			}
		}
	}*/

	// 插入排序
	for(Node* i=list->head->next->next,*j; i!=list->tail; i=i->next)
	{
		TYPE data = i->data;
		for(j=i; j!=list->head->next && data < j->prev->data; j=j->prev)
		{
			j->data = j->prev->data;
		}
		j->data = data;
	}
}

int main(int argc,const char* argv[])
{
	DoubleList* list = create_list();
	for(int i=0; i<10; i++)
	{
		add_head_list(list,rand()%100);
	}
	sort_list(list);
	show_list(list);
	/*
	show_list(list);
	del_head_list(list);
	show_list(list);
	del_tail_list(list);
	show_list(list);
	*/
	return 0;
}

双向循环链表

头节点的前驱是尾结点,尾结点的后继是头结点,当链表只有一个节点时,前驱和后继都指向自己

所以操作双向循环链表,不存在空指针

优点:头和尾共用一个空白节点


内核链表

在linux内存中有一种通用的双向循环链表,链表要想做到通用,就无法知道节点中存储什么数据,无法明确链表的节点,Linux内核中的链表的节点struct list_head只设计了指针域,也就是节点中只有两个前驱指针和后继指针,linux内核中的链表只负责这些节点连接起来。

链表的使用者自己设计结构体,结构以要包含struct list_head类型的成员,在链表接口时只需要提供struct list_head类型的节点成员,在依靠他们串联起所有数据


问题:遍历链表时只能通过链表中的struct list_head类型的成员,所以必须有一个工具,可以根据成员访问结构体。

#define list_entry(ptr,type member)\
((type *)((char *))

//1.假定0地址是某个结构体变量的地址,然后使用->访问 struct list_head类型的成员,然后使用&地址计算出struct list_head类型的成员在结构体中的第几个字节,然后把地址转换成整数
(unsigned long)(&((type *)0)->member)
//2.把struct list_head类型的成员的地址转换成进步值为1的char*类型.然后在减去struct list_head类型的成员到结构体开头的距离,就计算出结构变量的首地址,然后在强制转换成结构体类型的地址
(char*)(ptr)
通用链表

内核链表设计的很好,但不利于初学者使用,另一种通用的思路就是借助

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值