第2章 线性表(Linear List)
2.1 线性表的定义与基本概念
线性表是数据结构的一种
定义:n(≥0)个具有相同特性数据元素的有限序列
线性表的特性有以下几点 :
- 线性表是由零个或多个数据元素组成的有限序列,每个元素都是单个元素
- 线性表中必存在唯一的一个第一个元素和最后一个元素
- 除了第一个和最后一个元素外,每个元素都有唯一的前驱和后继
- 线性表可以用顺序存储结构或链式存储结构来实现,顺序存储结构可以实现随机存取,链式存储结构可以方便地插入和删除
线性表的抽象数据类型定义
2.2 线性表的顺序存储和实现
存储方式:用一组地址连续的存储单元顺序存放线性表中逻辑相邻的元素。假定每个元素需占用d个存储单元,则n个元素的线性表存储为:
优缺点
优点:实现相对简单——直接使用数组语法支持随机存取
缺点:插入、删除操作需要大量移动元素,需要预先分配内存空间——可能造成浪费,解决方案—链表
顺序表定义
//定义顺序表的结构体
typedef struct {
//用数组存储数据元素
int array[100];
//记录顺序表的当前长度
int size;
}sequential_list;
顺序表初始化
void seq_list(sequential_list* L) {
L.size = 0;
}
顺序表插入
void insert_element(sequential_list* L, int index, int value) {
//检查index是否合法
if (index < 0 || index > L->size) {
printf("索引无效");
return;
}
//从后往前移动元素,为插入位置腾出空间
for (int i = 98; i >= index; i--) {
L->array[i + 1] = L->array[i];
}
//在index位置赋值为value
L->array[index] = value;
L->size++;
}
顺序表删除
//删除方法,并返回删除的值
int delete_element(sequential_list* L, int index) {
//检查index是否合法
if (index < 0 || index > L->size) {
printf("索引无效");
return -1;
}
//记录被删除的值
int result = L->array[index];
//从前往后移动元素,覆盖被删除的位置
for (int i = index + 1; i < 99; i++) {
L->array[i - 1] = L->array[i];
}
//更新有效元素个数
L->size--;
//返回被删除的值
return result;
}
顺序表查找
//查找方法,在顺序表中查找value并返回其位置,如果不存在则返回-1
int search_element(sequential_list* L, int value) {
//遍历数组,比较每个元素和value
for (int i = 0; i < 100; i++) {
if (L.array[i] == value) {
//如果找到了,返回其位置
return i;
}
}
//如果没有找到,返回-1
return -1;
}
顺序表遍历
//打印顺序表
void print_element(sequential_list* L) {
for (int i = 0; i < L->size; i++) {
printf("%d ", L->array[i]);
}
printf("\n");
}
2.3 线性表的链式存储和实现
存储方式:
用一组任意的存储单元来存储线性表中的结点。用指针(即链)实现线性表中各结点的逻辑关系。
- 根据链接方式不同,分为∶单链表、循环链表、双向链表
- 根据链表实现不同,分为︰静态链表、动态链表
优缺点
优点无需占据连续内存,按需申请内存,不会造成浪费,插入、删除时无需移动元素(但T(n)仍为O(n)
缺点:不支持随机存取——按位置找元素时需要遍历链,仅支持单向(从表头到表尾)遍历
单链表定义
//定义结构体类型
typedef struct node {
//数据域
int data;
//指针域
struct node* next;
}node;
//创建节点
node* create_node(int data) {
//分配内存空间
node* new_node = malloc(sizeof(node));
//初始化节点
new_node->data = data;
new_node->next = NULL;
//返回节点的地址
return new_node;
}
单链表插入
//单链表插入
void insert_at_begin(node** head, int data) {
//创建新节点
node* new_node = create_node(data);
//新节点的next指向头节点
new_node->next = *head;
//头指针变成新节点
*head = new_node;
}
void insert_at_end(node** head, int data) {
//创建新节点
node* new_node = create_node(data);
//不存在头节点
if (*head == NULL) {
//头节点变为新节点
*head = new_node;
return;
}
//遍历链表
node* current = *head;
while (current->next != NULL) {
current = current->next;
}
//新节点连接到最后节点
current->next = new_node;
}
单链表删除
//单链表删除
void delete_node(node** head, int data) {
node* current = *head;
node* previous = NULL;
//遍历寻找data值
while (current->next != NULL) {
if (current->data == data) {
//如果在第一个节点
if (previous == NULL) {
//头节点成为下一个节点
*head = current->next;
}
else {
//前继节点指向下下一个节点
previous->next = current->next;
}
free(current);
return;
}
previous = current;
current = current->next;
}
}
单链表遍历
//单链表打印
void print_node(node* head) {
//遍历链表
node* current = head;
while (current != NULL) {
//输出数据
printf("%d", current->data);
current = current->next;
}
printf("\n");
}
2.3.2 单向循环链表
存储特点:单向循环链表是单向链表的变形。单向循环链表最后一个结点的指针域指向头结点使整个链表形成一个环
形式一∶带头指针的单向循环链表
形式二∶带尾指针的单向循环链表
2.3.3 双向链表
存储特点:每个结点有两个指针域,其中一个指向直接后继,一个指向直接前驱。
结构特点:
形式一∶双向链表
形式二:双向循环链表
完整代码
C语言顺序表
#include <stdio.h>
#include <string.h>
//定义顺序表的结构体
typedef struct {
//用数组存储数据元素
int array[100];
//记录顺序表的当前长度
int size;
} sequential_list;
void seq_list(sequential_list *L);
void insert_element(sequential_list* L, int index, int value);
int delete_element(sequential_list* L, int index);
int search_element(sequential_list* L, int value);
void print_element(sequential_list* L);
int main()
{
sequential_list L;
seq_list(&L);
insert_element(&L, 0, 1);
insert_element(&L, 1, 3);
insert_element(&L, 2, 2);
print_element(&L);
delete_element(&L, 1);
print_element(&L);
return 0;
}
//初始化
void seq_list(sequential_list* L) {
L->size = 0;
}
//添加方法
void insert_element(sequential_list* L, int index, int value) {
//检查index是否合法
if (index < 0 || index > L->size) {
printf("索引无效\n");
return;
}
//从后往前移动元素,为插入位置腾出空间
for (int i = 98; i >= index; i--) {
L->array[i + 1] = L->array[i];
}
//在index位置赋值为value
L->array[index] = value;
L->size++;
}
//删除方法,并返回删除的值
int delete_element(sequential_list* L, int index) {
//检查index是否合法
if (index < 0 || index > L->size) {
printf("索引无效");
return -1;
}
//记录被删除的值
int result = L->array[index];
//从前往后移动元素,覆盖被删除的位置
for (int i = index + 1; i < 99; i++) {
L->array[i - 1] = L->array[i];
}
//更新有效元素个数
L->size--;
//返回被删除的值
return result;
}
//查找方法,在顺序表中查找value并返回其位置,如果不存在则返回-1
int search_element(sequential_list* L, int value) {
//遍历数组,比较每个元素和value
for (int i = 0; i < 100; i++) {
if (L->array[i] == value) {
//如果找到了,返回其位置
return i;
}
}
//如果没有找到,返回-1
return -1;
}
//打印顺序表
void print_element(sequential_list* L) {
for (int i = 0; i < L->size; i++) {
printf("%d ", L->array[i]);
}
printf("\n");
}
Java顺序表
import java.util.Arrays;
public class SequentialList {
//数组
private int[] array;
//有效元素个数
private int size;
//构造方法,初始化数组和大小
public SequentialList(int capacity) {
array = new int[capacity];
size = 0;
}
//插入方法,在index位置插入value
public void insertElement(int index, int value) {
//检查index是否合法
if (index < 0 || index > size) {
System.out.println("索引无效");
return;
}
//检查数组是否已满
if (size == array.length) {
System.out.println("数组已满");
return;
}
//从后往前移动元素,为插入位置腾出空间
for (int i = size - 1; i >= index; i--) {
array[i + 1] = array[i];
}
//在index位置赋值为value
array[index] = value;
size++;
}
//删除方法,在index位置删除元素,并返回被删除的值
public int deleteElement(int index) {
//检查index是否合法
if (index < 0 || index > size) {
System.out.println("索引无效");
return -1;
}
//记录被删除的值
int result = array[index];
//从前往后移动元素,覆盖被删除的位置
for (int i = index + 1; i < size; i++) {
array[i - 1] = array[i];
}
//更新有效元素个数
size++;
//返回被删除的值
return result;
}
//查找方法,在顺序表中查找value并返回其位置,如果不存在则返回-1
public int searchElement(int value) {
//遍历数组,比较每个元素和value
for (int i = 0; i < size; i++) {
if (array[i] == value) {
//如果找到了,返回其位置
return i;
}
//如果没有找到,返回-1
}
return -1;
}
@Override
public String toString() {
return "SequentialList{" +
"array=" + Arrays.toString(array) +
", size=" + size +
'}';
}
}
C语言单链表
#include <stdio.h>
#include <stdlib.h>
//定义结构体类型
typedef struct node {
//数据域
int data;
//指针域
struct node* next;
}node;
//创建节点
node* create_node(int data) {
//分配内存空间
node* new_node = malloc(sizeof(node));
//初始化节点
new_node->data = data;
new_node->next = NULL;
//返回节点的地址
return new_node;
}
//单链表插入
void insert_at_begin(node** head, int data) {
//创建新节点
node* new_node = create_node(data);
//新节点的next指向头节点
new_node->next = *head;
//头指针变成新节点
*head = new_node;
}
void insert_at_end(node** head, int data) {
//创建新节点
node* new_node = create_node(data);
//不存在头节点
if (*head == NULL) {
//头节点变为新节点
*head = new_node;
return;
}
//遍历链表
node* current = *head;
while (current->next != NULL) {
current = current->next;
}
//新节点连接到最后节点
current->next = new_node;
}
//单链表删除
void delete_node(node** head, int data) {
node* current = *head;
node* previous = NULL;
//遍历寻找data值
while (current->next != NULL) {
if (current->data == data) {
//如果在第一个节点
if (previous == NULL) {
//头节点成为下一个节点
*head = current->next;
}
else {
//前继节点指向下下一个节点
previous->next = current->next;
}
free(current);
return;
}
previous = current;
current = current->next;
}
}
//单链表打印
void print_node(node* head) {
//遍历链表
node* current = head;
while (current != NULL) {
//输出数据
printf("%d", current->data);
current = current->next;
}
printf("\n");
}
int main()
{
node* head = NULL;
insert_at_begin(&head, 1);
insert_at_begin(&head, 2);
insert_at_begin(&head, 3);
insert_at_begin(&head, 4);
insert_at_begin(&head, 5);
print_node(head);
delete_node(&head, 3);
print_node(head);
return 0;
}
Java单链表
//定义节点类
class Node {
//数据域
int data;
//指针域
Node next;
//无参构造方法
public Node() {
}
//有参构造方法
public Node(int data) {
this.data = data;
this.next = null;
}
}
//定义单链表类
class LinkedList {
//头节点
Node head;
public void linkedList() {
this.head = null;
}
//头插法
public void headInsert(int[] arr) {
for (int i = 0; i < arr.length; i++) {
Node node = new Node(arr[i]);
node.next = head;
head = node;
}
}
//尾插法
public void tailInsert(int[] arr) {
// 尾结点
Node tail = new Node();
for (int i = 0; i < arr.length; i++) {
Node node = new Node(arr[i]);
// 如果链表为空,将头结点和尾结点都指向新结点
// 如果链表不为空,将尾结点的next指向新结点,然后将尾结点更新为新结点
if (head == null) {
head = node;
tail = node;
} else {
tail.next = node;
tail = node;
}
}
}
public void printList() {
// 辅助指针p,遍历链表
Node p = head;
// 当p不为空时,打印当前结点的值,并将p移动到下一个结点
while (p != null) {
System.out.print(p.data + " ");
p = p.next;
}
// 换行
System.out.println();
}
}
public class Test {
public static void main(String[] args) {
// 创建一个单链表类的对象
LinkedList list = new LinkedList();
// 创建一个数组,用来存放数据
int[] arr = {1, 2, 3, 4, 5};
// 使用头插法创建单链表
list.headInsert(arr);
// 打印单链表,应该输出5->4->3->2->1
list.printList();
}
}