线性结构
在数据元素的非空有限集中:
- 存在唯一的一个被称作“第一个”的数据元素
- 存在唯一的一个被称作“最后一个”的数据元素
- 除第一个之外,集合中的每个数据元素均只有一个前驱
- 除最后一个之外,集合中的每个数据元素均只有一个后继
线性表
线性表是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)
通用链表
内核链表设计的很好,但不利于初学者使用,另一种通用的思路就是借助