【数据结构与算法(C语言)】线性表

文章目录

  • 一、线性表(Linear list)
    • 1. 线性表功能
    • 2. 线性表分类
      • 2.1 顺序表(Sequenatial list)
      • 2.2 单链表(Singly linked list)
      • 2.3 双链表(Double linked lists)
      • 2.4 循环链表((Double)Circylar linked lists)
      • 2.5 栈(Stack)
      • 2.6 (循环)队列(Queue)

文章为学习笔记记录,学习内容来自b站 《数据结构与算法 已完结(CLion 2022 最新版)》
后续会学到哪里就将笔记更新到哪里
不会记录太多内容,基本的编码实现C语言掌握好了,实现较为简单,主要是理解“结构”(物理、逻辑结构)

一、线性表(Linear list)

线性表的几个特征和概念:

  • 数据元素同类型
  • 有序序列
  • 线性结构
  • 长度
  • 表头
  • 表尾
  • 空表

1. 线性表功能

  • 初始化 init
  • 插入 insert
  • 删除 delete
  • 索引 get
  • 查找 find

2. 线性表分类

  1. 顺序存储:顺序表
  2. 链式存储:单链表、双链表(双向链表)、循环链表

解释:
1.顺序表是以数组为基础实现的(采用动态申请堆内存)
2.链式表中的循环链表只是单纯的将表尾和head表头链接起来形成,可分为循环单链表、循环双链表

2.1 顺序表(Sequenatial list)

时间复杂度 思考:

  • 插入 O(n)
  • 删除 O(n)
  • 索引 O(1)
  • 查找 O(n)

注意事项:顺序表是以数组为基础实现的,采用物理上顺序存储,访问可以实现按下标的随机访问,访问效率较高,查找、插入、删除需要挪动几乎遍历整个表,所以效率较低。

#include 
#include 

typedef int E;

struct Sequenatial_list {
	E * array_list;
	int size;
	int capacity;
};

typedef struct Sequenatial_list * Sq_List;

_Bool init_Sq_List(Sq_List list) {
	list->size = 0;
	list->capacity = 10;
	list->array_list = (E *)malloc(sizeof(E) * list->capacity);
	if (list->array_list == NULL)	return 0;
	return 1;
}

void print_Sq_List(Sq_List list) {
	for(int i=0; i<list->size; i++)
		printf("%d ", list->array_list[i]);
	printf("\n");
}

_Bool insert_Sq_List(Sq_List list, E element, int index) {
	if (index < 1 || index > list->size + 1)	return 0;
	if (list->size == list->capacity) {
		int newCapacity = list->capacity + (list->capacity >> 1);
		E *newArrayList = (E *)realloc(list->array_list, sizeof(E) * newCapacity);
		if (newArrayList == NULL)	return 0;
		list->array_list = newArrayList;
		list->capacity = newCapacity;
	}
	for (int i=list->size; i>index-1; i--) {
		list->array_list[i] = list->array_list[i-1];
	}
	list->array_list[index-1] = element;
	list->size++;
	return 1;
}

_Bool delete_Sq_List(Sq_List list, int index) {
	if (index < 1 || index > list->size)	return 0;
	for (int i=index-1; i<list->size-1; i++)
		list->array_list[i] = list->array_list[i+1];
	list->size--;
	return 1;
}

E *get_Sq_List(Sq_List list, int index) {
	if (index < 1 || index > list->size)	return NULL;
	return &list->array_list[index-1];
}

int find_Sq_List(Sq_List list, E element) {
	for (int i=0; i<list->size; i++) {
		if (list->array_list[i] == element)	return i+1;
	}
	return -1;
}

int size_Sq_List(Sq_List list) {
	return list->size;
}

int main() {
	struct Sequenatial_list sqlist;
	init_Sq_List(&sqlist);
	insert_Sq_List(&sqlist, 2, 1);
	insert_Sq_List(&sqlist, 1, 2);
	insert_Sq_List(&sqlist, 3, 2);
	insert_Sq_List(&sqlist, 4, 1);
	for (int i=0; i<20; i++)
		insert_Sq_List(&sqlist, i*10, i);
	delete_Sq_List(&sqlist, 2);
	print_Sq_List(&sqlist);
	printf("%d\n", size_Sq_List(&sqlist));
	return 0;
}

2.2 单链表(Singly linked list)

时间复杂度 思考:

  • 插入 O(n)
  • 删除 O(n)
  • 索引 O(n)
  • 查找 O(n)

注意事项:
1、虽然要先找到前驱结点,但单链表插入和删除不需要挪动后续的所有元素,所以效率比顺序表高一些;
2、单链表不支持随机访问,所以索引效率比顺序表要慢;
3、读取效率顺序表高,插入和删除效率链表要高

#include 
#include 

typedef int E;

struct Singly_linked_list {
	E element;
	struct Singly_linked_list *next;
};

typedef struct Singly_linked_list * Sl_List;

void init_Sl_list (Sl_List head) {
	head->next = NULL;
}

void pirnt_Sl_list (Sl_List head) {
	while (head->next != NULL) {
		head = head->next;
		printf("%d ", head->element);
	}
	printf("\n");
}

_Bool insert_Sl_list (Sl_List head, E element, int index) {
	if (index < 1)	return 0;
	while (--index) {
		if (head->next == NULL)	return 0;
		head = head->next;
	}
	Sl_List node = (struct Singly_linked_list *)malloc(sizeof(struct Singly_linked_list));
	if (node == NULL)	return 0;
	node->element = element;
	node->next = head->next;
	head->next = node;
	return 1;
}

_Bool delete_Sl_list (Sl_List head, int index) {
	if (index < 1)	return 0;
	while (--index) {
		head = head->next;
		if (head->next == NULL)	return 0;	// 注意这边后判断下一个结点是否为空,与 插入的先判断下一个结点是否为空,有什么区别?
	}										// 同样都是移动到index的前一个结点,后判断的会提早到NULL,考虑尾插和尾删时需要注意
	Sl_List temp = head->next;
	head->next = temp->next;
	free(temp);
	return 1;
}

E *get_Sl_list (Sl_List head, int index) {
	if (index < 1)	return 0;
	while (--index) {
		head = head->next;
		if (head->next == NULL)	return 0;
	}
	return &head->next->element;
}

int find_Sl_list (Sl_List head, E element) {
	for (int i=1; (head=head->next)!=NULL; i++) {
		if (head->element == element)	return i;
	}
	return -1;
}

int size_Sl_list (Sl_List head) {
	int i=0;
	do {
		i++;
	} while ((head=head->next) != NULL);
	return i-1;
}

int main() {
	struct Singly_linked_list sllist;
	init_Sl_list(&sllist);
	insert_Sl_list(&sllist, 2, 1); // 2
	insert_Sl_list(&sllist, 3, 1); // 3 2
	insert_Sl_list(&sllist, 5, 2); // 3 5 2	
	insert_Sl_list(&sllist, 7, 2); // 3 7 5 2
	delete_Sl_list(&sllist, 2);	   // 3 5 2
	insert_Sl_list(&sllist, 5, 3); // 3 5 5 2
	insert_Sl_list(&sllist, 1, 2); // 3 1 5 5 2		
	pirnt_Sl_list(&sllist);
	printf("%d\n", *get_Sl_list(&sllist, 1));
	printf("%d\n", find_Sl_list(&sllist, 5));
	printf("%d\n", size_Sl_list(&sllist));
	return 0;
}

2.3 双链表(Double linked lists)

双链表比单链表灵活的地方在于可以在任意一个结点处访问相邻结点,无需从头开始

#include 
#include 

typedef int E;

struct Double_linked_list {
	E element;
	struct Double_linked_list *next;
	struct Double_linked_list *prev;
};

typedef struct Double_linked_list * Dllist;

void init_Dl_list (Dllist head) {
	head->next = head->prev = NULL;
	
}

void print_Dl_list (Dllist head, _Bool rev) {
	if (rev == 0) {
		while(head = head->next)
			printf ("%d -> ", head->element);
		printf("\n");
	} else {
		while (head->next)
			head = head->next;
		while (head->prev)	{
			printf("%d <- ", head->element);
			head = head->prev;
		}
		printf("\n");
	}
}

_Bool insert_Dl_list (Dllist head, E element, int index) {
	if (index < 1)	return 0;
	for (; --index; ) {
		head = head->next;
		if (head == NULL)	return 0;
	}
	Dllist node = (struct Double_linked_list *)malloc(sizeof(struct Double_linked_list));
	if (node == NULL)	return 0;
	node->element = element;
	node->next = head->next;
	if (head->next)
		head->next->prev = node;
	node->prev = head;
	head->next = node;
	return 1;
}

_Bool delete_Dl_list (Dllist head, int index) {
	if (index < 1)	return 0;
	while ((head = head->next) && --index);
	if (head == NULL)	return 0;
	Dllist temp = head;
	head->prev->next = temp->next;
	if (temp->next)	temp->next->prev = head->prev;
	free(temp);
}

int main() {
	struct Double_linked_list head;
	init_Dl_list(&head);
	for (int i=1; i<=5; i++) {
		insert_Dl_list(&head, i*100, 1);
	}
	delete_Dl_list(&head, 2);
	print_Dl_list(&head, 0);
	print_Dl_list(&head, 1);
	return 0;
}

2.4 循环链表((Double)Circylar linked lists)

循环链表有单循环链表和双循环链表,比之链表灵活的地方在于头尾项链,可以从任意一个结点位置访问到任意一个结点位置。

程序实现方面:单链表是基础,双链表和循环链表和单链表的实现类似,注意边界问题及链接问题即可。

2.5 栈(Stack)

栈顶、栈底,只能从栈顶这头出栈和入栈(FILO)
顺序表和链表均能实现“栈”这一结构
一般入栈叫push,出栈叫pop

共享栈:将两个栈共用一个固定长度的数组,数组头尾分别为两个栈的栈底,当两个栈的栈顶指针之差绝对值为1时,说明数组空间存满了,此时两个栈均无法再入栈。这样的两个栈就叫共享栈(共用一个固定长度的数组空间)

// 两种方式实现栈(顺序表、链式表)
// 1、顺序表
#include 
#include 

typedef int E;

typedef struct Stack {
	E *array;
	int capacity;
	int top;	// 表示栈顶位置,存的是栈顶元素下标
} array_Stack;

typedef struct Stack * ArrayStack; 

_Bool initStack (ArrayStack stack) {
	stack->array = (E *)malloc(sizeof(E) * 10);
	if (stack->array == NULL)	return 0;
	stack->capacity = 10;
	stack->top = -1;
	return 1;
}

_Bool pushStack (ArrayStack stack, E element) {
	if(stack->top + 1 == stack->capacity) {
        int newCapacity = stack->capacity + (stack->capacity >> 1);
        E * newArray = (E *)realloc(stack->array, newCapacity * sizeof(E));
        if(newArray == NULL) return 0;
        stack->array = newArray;
        stack->capacity = newCapacity;
    }
    stack->array[++(stack->top)] = element;
    return 1;
}

_Bool isEmpty (ArrayStack stack) {
	return stack->top == -1;
}

E popStack (ArrayStack stack) {
	return stack->array[stack->top--];
}

void printStack(ArrayStack stack){
    printf("| ");
    for (int i = 0; i < stack->top + 1; ++i) {
        printf("%d ", stack->array[i]);
    }
    printf("\n");
}

int main() {
	array_Stack stack;
	initStack(&stack);
	pushStack(&stack, 7);
	pushStack(&stack, 6);
	pushStack(&stack, 5);
	pushStack(&stack, 4);
	pushStack(&stack, 3);
	pushStack(&stack, 2);
	pushStack(&stack, 1);
	printStack(&stack);
	while (!isEmpty(&stack)) {
		printf("%d ", popStack(&stack));
	}
	printf("\n");
	return 0;
}


// 2、链式表
#include 
#include 

typedef int E;

struct LinkedStack {
	E element;
	struct LinkedStack *next;
};

typedef struct LinkedStack * LL_Stack;

void initStack (LL_Stack stack) {
	stack->next = NULL;
}

_Bool pushStack (LL_Stack stack, E element) {
	LL_Stack node = (struct LinkedStack *)malloc(sizeof(struct LinkedStack));
	if (node == NULL)	return 0;
	node->element = element;
	node->next = stack->next;
	stack->next = node;
	return 1;
}

_Bool isEmpty (LL_Stack stack) {
	return stack->next == NULL;
}

E popStack (LL_Stack stack) {
	LL_Stack top = stack->next;
    stack->next = stack->next->next;
    E e = top->element;
    free(top);  //别忘了释放结点的内存
    return e;   //返回出栈元素
}

void printStack (LL_Stack stack) {
	while (stack = stack->next) {
		printf("%d ", stack->element);
	}
	printf("\n");
}

int main() {
	struct LinkedStack stack;
	initStack(&stack);
	pushStack(&stack, 7);
	pushStack(&stack, 6);
	pushStack(&stack, 5);
	pushStack(&stack, 4);
	pushStack(&stack, 3);
	pushStack(&stack, 2);
	pushStack(&stack, 1);
	printStack(&stack);
	while (!isEmpty(&stack)) {
		printf("%d ", popStack(&stack));
	}
	return 0;
}

2.6 (循环)队列(Queue)

理解循环队列的入队和出队
开始时队首和队尾标志指向同一个位置(且均是空位置)
队首永远指向空位置(队首往后走,则队头必清空——出队)
队尾永远指向下一个入队数据(队尾往后走,队末必入数——入队)
入队:队尾标志后移一个位置,然后写入数据到队尾
出队:队首标志后移一个位置,然后把队首释放数据
为了不无限制的拉长队伍,浪费空间,可以采用循环队列(利用求余实现循环)
【数据结构与算法(C语言)】线性表_第1张图片
如何区分固定长度的队伍有 空位置、队空或队满 呢?
队空:队首和队尾处在同一个位置
思考——>既然队空时用队尾/首在同一个位置判断,那队满怎么判断?仍用同一个位置则有歧义
因此,考虑空出一个位置(即牺牲一个空间),当队尾和队首差绝对值为1的位置时,判定队满,当队尾和队首在同一个位置时,则队空

// 公式1:
(队尾 + 1) % 固定的队伍长度 == 队首	// 判定队满
// 举例:固定队伍长度为10
队尾	6	队首	7	差距	1(6+1)%10 == 7	判定	队满
队尾	0	队首	1	差距	1(0+1)%10 == 1	判定	队满
队尾	2	队首	1	差距	1(2+1)%10 == 3	判定	队没满
队尾	9	队首	1	差距	7(9+1)%10 == 0	判定	队没满
队尾	2	队首	3	差距	1(2+1)%10 == 3	判定	队满
通过以上举例可以看出,若要判定队空,可以采用如下伪代码:
// 公式2:
(队首 + 1) % 固定的队伍长度 == 队尾	// 判定队空

总而言之,循环队列只需记住2个公式(记住公式,后面慢慢熟悉理解再消化)
队满:(队尾 + 1) % 固定的队伍长度 == 队首 实际上此时的队尾标志不能再往前走了!
队空:(队首 + 1) % 固定的队伍长度 == 队尾 此时的队首仍要往前走,把最后一个数清空,才能实现队空

// 队列:顺序表实现(循环队列)
#include 
#include 

typedef int E;

struct Queue {
	E *array;
	int capacity;		// 固定队伍容量
	int rear, front;	// 队尾、队首标志
};

typedef	struct Queue * ArrayQueue;

_Bool initQueue (ArrayQueue queue) {
	queue->capacity = 10;
	queue->array = (E *)malloc(sizeof(E) * queue->capacity);
	if (queue->array == NULL)	return 0;
	queue->rear = queue->front = 0;	// 默认队首、队尾指向位置0
	return 1;
}

void printQueue (ArrayQueue queue) {
	printf("<<< ");
	int i = queue->front;
	do {
		i = (i + 1) % queue->capacity;
		printf("%d ", queue->array[i]);
	} while (i != queue->rear);	// 队尾标志处也是有数据的,队首标志处是准备出队的数据的,一开始队首标志处就是没数据的
	printf("<<<\n");
}

_Bool offerQueue (ArrayQueue queue, E element) {
	if ((queue->rear+1) % queue->capacity == queue->front)	return 0;	// 队满判定
	queue->rear = (queue->rear + 1) % queue->capacity;	// 为了实现循环,用取余,不能直接用queue->rear++
	queue->array[queue->rear] = element;
	return 1;
}

_Bool isEmpty (ArrayQueue queue) {
	return queue->front == queue->rear;
}

E pollQueue (ArrayQueue queue) {
	queue->front = (queue->front + 1) % queue->capacity;
	return queue->array[queue->front];
}

int main (){
	struct Queue queue;
	initQueue(&queue);
	offerQueue(&queue, 3);
	offerQueue(&queue, 5);
	offerQueue(&queue, 4);
	offerQueue(&queue, 1);
	offerQueue(&queue, 2);
	offerQueue(&queue, 6);
	offerQueue(&queue, 7);
	offerQueue(&queue, 1);
	offerQueue(&queue, 9);	// 只能存9个数,要入第10个数时会被判定队满无法入队
	offerQueue(&queue, 0);
	offerQueue(&queue, 4);
	printQueue(&queue);
	// while (!isEmpty(&queue)) {
	// 	printf("%d ", pollQueue(&queue));
	// }
	pollQueue(&queue);
	offerQueue(&queue, 0);
	printQueue(&queue);
	pollQueue(&queue);
	offerQueue(&queue, 4);
	printQueue(&queue);
	printf("\n");
	return 0;
}

// 队列:链式表实现(不需要考虑循环问题)
// 采用链式队列就不用去关心循环结构了,也就不用求余
// 链式队列入队则新增结点,出队则释放结点
#include 
#include 

typedef int E;

struct LNode {
	E element;
	struct LNode *next;
};

typedef struct LNode * Node;

struct Queue{
	Node front, rear;
};

typedef struct Queue * LinkedQueue;

_Bool initQueue (LinkedQueue queue) {
	Node node = (struct LNode *)malloc(sizeof(struct LNode));
	if (node == NULL)	return 0;
	queue->front = queue->rear = node;
	return 1;
}

_Bool offerQueue (LinkedQueue queue, E element) {
	Node node = (struct LNode *)malloc(sizeof(struct LNode));
	if (node == NULL)	return 0;
	node->element = element;
	queue->rear->next = node;
	queue->rear = node;
	return 1;
}

E pollQueue (LinkedQueue queue) {
	Node node = queue->front->next;
	E e = node->element;
	queue->front->next = queue->front->next->next;
	if (node == queue->rear)	queue->rear = queue->front;
	free(node);
	return e;
}

_Bool isEmpty (LinkedQueue queue) {
	return queue->front == queue->rear;
}

void printQueue (LinkedQueue queue) {
	printf("<<< ");
	Node node = queue->front->next;
	while (1) {
		printf("%d ", node->element);
		if (node == queue->rear)	break;
		else node = node->next;
	}
	printf("<<<\n");
}

int main () {
	struct Queue queue;
	initQueue(&queue);
	offerQueue(&queue, 4);
	offerQueue(&queue, 7);
	offerQueue(&queue, 6);
	offerQueue(&queue, 1);
	offerQueue(&queue, 2);
	offerQueue(&queue, 9);
	printQueue(&queue);
	while (!isEmpty(&queue)) {
		printf("%d ", pollQueue(&queue));
	}
	return 0;
}

你可能感兴趣的:(数据结构与算法,数据结构,c语言)