数据结构
什么是数据结构
数据结构的起源
-
1968年,美国高德纳教授,《计算机程序设计艺术》 第一卷《基本算法》出版,开创了数据结构与算法的先河
-
数据结构是一门研究数据之间关系和操作的学科,而非计算方法
-
数据结构 + 算法 = 程序 由沃思提出,凭借该论点获得图灵奖,这句话揭示了程序的本质
-
推荐书籍:程杰《大话数据结构》
数据结构的基本概念
数据:
- 所有能够输入到计算机中,能被程序处理的,能描述客观事物特征的符号
数据项:
- 有独立含义数据的最小单位,也叫数据域、域
数据元素:
-
组成数据的,有一定含义的基本单位,也叫节点、结点、记录
-
一个数据元素由若干个数据项组成
数据结构:
- 相互之间存在一种或者多种特定关系的数据元素的集合
算法:
- 数据结构具备的某些功能,能够解决某种特定问题的方法
数据结构研究的三个方面
-
数据的逻辑结构
-
数据的存储结构(物理结构)
-
数据结构的运算
逻辑结构与存储结构
数据的逻辑结构:
集合:
- 数据元素同属于一个集体,但是元素之间没有任何关系
线性结构(表):
- 数据元素之间存在一对一的关系
树型结构(树):
- 数据元素之间存在一对多的关系
图型结构(图):
- 数据元素之间存在多对多的关系
数据的物理结构:
*顺序结构:
-
数据元素存储在连续的内存中,用数据元素的相对位置来表示之间的关系
-
优点:支持随机访问、访问效率高、适合查找数据
-
缺点:对内存要求高、空间利用率低,插入和删除很麻烦
-
*链式结构:
-
数据元素存储在彼此相互独立的内存空间中,每个独立的数据元素称为节点,每个节点中增加一个数据项用于存储其他节点的地址(指针域),用于表示数据节点之间的关系
- 优点:对内存要求低、空间利用率高,插入和删除很方便
- 缺点:不支持随机访问,只能从前到后逐个访问
逻辑结构与物理结构的关系:
- 表 顺序、链式
- 树 链式、顺序
- 图 顺序 + 链式、顺序
- 每种逻辑结构采用什么存储结构没有明确规定,通常是根据实现的难度以及空间、时间方面的要求,来选择最合适的物理结构
数据结构的运算
建立数据结构 create
销毁数据结构 destroy
清空数据结构 clean
插入元素 insert add
删除元素 delete
访问元素 access
修改元素 modify
查询元素 query
数据结构排序 sort
遍历数据结构 show or print or ergodic
顺序表和链式表的实现
顺序表:
- 数组
数据项:
- 存储连续元素的内存首地址
- 表的容量
- 元素的数量
运算:
-
创建、销毁、清空、插入、删除、访问、查询、修改、排序、遍历
- 注意:
- 1、从始至终保持元素的连续性
- 2、不能越界
- 优点:支持随机访问,修改、访问、排序效率高、不容易出现内存碎片
- 缺点:对内存要求较高,插入、删除元素时不方便、效率低
- 注意:
顺序表举例:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#define TYPE int
// 设计顺序表结构
typedef struct Array
{
TYPE* ptr; // 存储元素的内存首地址
size_t cal; // 表的容量
size_t cnt; // 元素的数量
}Array;
// 创建
Array* create_array(size_t size)
{
// 分配表结构内存
Array* arr = malloc(sizeof(Array));
// 分配存储元素的内存
arr->ptr = malloc(sizeof(TYPE)*size);
// 记录容量
arr->cal = size;
// 初始化数量
arr->cnt = 0;
return arr;
}
// 销毁
void destroy_array(Array* arr)
{
free(arr->ptr);
free(arr);
}
// 清空
void clean_array(Array* arr)
{
arr->cnt = 0;
}
// 插入
bool insert_array(Array* arr,size_t index,TYPE val)
{
// 满?
if(arr->cnt >= arr->cal) return false;
// 判断下标,为了确保表中元素的连续性
if(index > arr->cnt) return false;
/*
for(int i=arr->cnt; i>index; i--)
{
arr->ptr[i] = arr->ptr[i-1];
}
*/
//void *memmove(void *dest, const void *src, size_t n);
// 后移
memmove(arr->ptr+index+1,arr->ptr+index,(arr->cnt-index)*sizeof(TYPE));
arr->ptr[index] = val;
arr->cnt++;
return true;
}
// 删除
bool delete_array(Array* arr,size_t index)
{
if(index >= arr->cnt) return false;
// 前移
memmove(arr->ptr+index,arr->ptr+index+1,
(arr->cnt-index-1)*sizeof(TYPE));
arr->cnt--;
return true;
}
// 访问
bool access_array(Array* arr,size_t index,TYPE* val)
{
if(index >= arr->cnt) return false;
*val = arr->ptr[index];
return true;
}
// 修改
bool modify_array(Array* arr,size_t index,TYPE val)
{
if(index >= arr->cnt) return false;
arr->ptr[index] = val;
return true;
}
// 查询
int query_array(Array* arr,TYPE val)
{
for(int i=0; i<arr->cnt; i++)
{
if(val == arr->ptr[i]) return i;
}
return -1;
}
// 排序
void sort_array(Array* arr)
{
for(int i=0; i<arr->cnt-1; i++)
{
for(int j=i+1; j<arr->cnt; j++)
{
if(arr->ptr[i]>arr->ptr[j])
{
TYPE temp = arr->ptr[i];
arr->ptr[i] = arr->ptr[j];
arr->ptr[j] = temp;
}
}
}
}
// 遍历
void show_array(Array* arr)
{
for(int i=0; i<arr->cnt; i++)
{
printf("%d ",arr->ptr[i]);
}
printf("\n");
}
int main(int argc,const char* argv[])
{
TYPE val = -10;
Array* arr = create_array(10);
for(int i=0; i<5;i++)
{
insert_array(arr,0,i);
}
//show_array(arr);
//access_array(arr,1,&val);
//printf("%d\n",val);
//insert_array(arr,5,88);
//show_array(arr);
//delete_array(arr,0);
//show_array(arr);
//modify_array(arr,1,5);
//show_array(arr);
//printf("%d\n",query_array(arr,4));
//sort_array(arr);
//show_array(arr);
}
链式表List:
- 链表
每个节点Node数据项:
- 数据域:可以是若干个任意类型的数据项
- 指针域:指向下一个节点
- 把若干个节点通过指针域连接在一起就形成了链式表
不带头节点的链式表:
-
第一个节点的数据域存储的是有效数据的链表
-
缺点:头插入节点时,会修改指向第一个节点的指针,参数就需要传递二级指针;
-
删除节点时,需要分两种情况:
- 1、待删除的是第一个节点时,也会修改指向第一个节点的指针,就需要传递二级指针
- 2、待删除的不是第一个节点时,就正常删除
#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 add_head_list(Node** head,TYPE data)
{
Node* node = create_node(data);
node->next = *head;
*head = node;
}
// 按值删除
bool delete_value_list(Node** head,TYPE data)
{
// 如果删除头节点
if((*head)->data == data)
{
Node* temp = *head;
*head = temp->next;
free(temp);
return true;
}
for(Node* n=*head; NULL!=n->next; n=n->next)
{
if(n->next->data == data)
{
// 备份待删除节点
Node* temp = n->next;
// 连接待删除节点的前一个和后一个节点
n->next = temp->next;
// 删除待删除节点
free(temp);
return true;
}
}
return false;
}
// 按位置删除
bool delete_index_list(Node** head,size_t index)
{
// 如果删除头节点
if(0 == index)
{
Node* temp = *head;
*head = temp->next;
free(temp);
return true;
}
Node* n = *head;
while(--index > 0)
{
if(NULL == n->next) return false;
n = n->next;
}
if(NULL == n->next) return false;
// n是待删除节点的前一个
// 备份待删除节点
Node* temp = n->next;
// 连接待删除节点的前一个和后一个节点
n->next = temp->next;
// 删除待删除节点
free(temp);
return true;
}
//访问
bool access_list(Node* head,size_t index,TYPE* val)
{
Node* n = head;
for(int i=0; i<index; i++)
{
n = n->next;
if(NULL == n) return false;
}
*val = n->data;
return true;
}
//遍历
void show_list(Node* head)
{
for(Node* n=head; NULL!=n; n=n->next)
{
printf("%d ",n->data);
}
printf("\n");
}
// 排序
void sort_list(Node* head)
{
for(Node* i=head; NULL!=i->next; i=i->next)
{
for(Node* j=i->next; NULL!=j; j=j->next)
{
if(i->data > j->data)
{
TYPE temp = i->data;
i->data = j->data;
j->data = temp;
}
}
}
}
int main(int argc,const char* argv[])
{
Node* head = NULL;
for(int i=0;i<10; i++)
{
add_head_list(&head,i+10);
}
//delete_value_list(&head,19);
//delete_index_list(&head,9);
show_list(head);
TYPE val = -10;
access_list(head,0,&val);
printf("%d\n",val);
sort_list(head);
show_list(head);
/*
Node* n1 = create_node(10);
Node* n2 = create_node(20);
Node* n3 = create_node(30);
Node* n4 = create_node(40);
Node* n5 = create_node(50);
n1->next = n2;
n2->next = n3;
n3->next = n4;
n4->next = n5;
for(Node* n=n1; NULL!=n;n=n->next)
{
printf("%d ",n->data);
}*/
}
带头节点的链式表:
-
第一个节点的数据域不用于存储有效数据的链表,该头节点是专门用于指向第一个有效数据的节点
-
优点:无论是否头插入还是头删除,头节点永远不变,变的只是头结点的next,所以不用传递二级指针,也不用额外处理头删除
-
缺点:额外会多一个头节点
#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 add_head_list(Node* head,TYPE data)
{
Node* node = create_node(data);
node->next = head->next;
head->next = node;
}
// 尾添加
void add_tail_list(Node* head,TYPE data)
{
Node* n = head;
Node* tail = create_node(data);
while(NULL != n->next) n = n->next;
n->next = tail;
}
// 按位置删除
bool del_index_list(Node* head,size_t index)
{
Node* n = head;
for(int i=0; i<index; i++)
{
n = n->next;
if(NULL == n->next) return false;
}
Node* temp = n->next;
n->next = temp->next;
free(temp);
return true;
}
// 按值删除
bool del_value_list(Node* head,TYPE val)
{
Node* n = head;
for(Node* i=n; NULL!=n->next; n=n->next)
{
if(val == n->next->data)
{
Node* temp = n->next;
n->next = temp->next;
free(temp);
return true;
}
}
return false;
}
// 插入
bool insert_list(Node* head,size_t index,TYPE val)
{
Node* n = head;
Node* node = create_node(val);
for(int i=0; i<index; i++)
{
n = n->next;
if(NULL == n->next) return false;
}
node->next = n->next;
n->next = node;
return true;
}
// 按位置修改
bool modify_index_list(Node* head,size_t index,TYPE val)
{
Node* n = head->next;
for(int i=0; i<index; i++)
{
n = n->next;
if(NULL == n) return false;
}
n->data = val;
return true;
}
// 按值修改
bool modify_value_list(Node* head,TYPE old,TYPE val)
{
for(Node* n=head->next; NULL!=n; n=n->next)
{
if(old == n->data)
{
n->data = val;
return true;
}
}
return false;
}
// 访问
bool access_list(Node* head,size_t index,TYPE* val)
{
Node* n = head->next;
for(int i=0; i<index; i++)
{
if(NULL == n) return false;
n = n->next;
}
if(NULL == n) return false;
*val = n->data;
return true;
}
// 查询
int query_list(Node* head,TYPE val)
{
Node* n = head->next;
for(int i=0; n; i++,n=n->next)
{
if(val == n->data) return i;
}
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 temp = i->data;
i->data = j->data;
j->data = temp;
}
}
}
}
// 遍历
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* head = create_node(0);
for(int i=0; i<10; i++)
{
//add_head_list(head,i+10);
add_tail_list(head,i+10);
}
//del_index_list(head,5);
//del_value_list(head,25);
//insert_list(head,25,5);
//modify_index_list(head,9,5);
//modify_value_list(head,98,5);
/*
TYPE num = -10;
access_list(head,5,&num);
printf("%d\n",num);
*/
//printf("%d\n",query_list(head,19));
sort_list(head);
for(Node* i=head->next; NULL!=i; i=i->next)
{
printf("%d ",i->data);
}
//show_list(head);
}
注意:做题时,如果没有说明是否带头节点,那么两种情况都进行考虑