数据结构 顺序表与链表

数据元素是数据的基本单位 数据元素不能再分 原子项 数据元素可以再分 组合项

若干数据项组成一个数据元素

数据项是数据的最小单位

算法 :有穷性 确切性 输入项 输出项 可行性

时间复杂度 : O(1) O(logn) O(n) O(nlogn)O(n2)O(n3)O(2n)O(n!)

线性表中所有元素的数据类型都是相同的

一:综述

程序 = 算法 + 数据结构

数据结构 = 结构定义 + 结构操作

*1.顺序表与链表 2.栈与队列 *3.树与二叉树 4.图的存储与遍历 5.排序与查找 *6.平衡二叉查找树 7.堆与优先队列 8.森林和并查集 9.图论算法入门 *10.字符串匹配算法 (kmp,shiftand, 字典树 ,双数组字典树(离线查找)) ac自动机 ac自动机的线索化

区别

顺序表 vector 存储空间连续

非连续的存储结构

二:顺序表

1.在O(1)的时间复杂度内查找到元素

2.连续的存储空间

3.属性:1.整个顺序表的总长度size

2.length一共存储了多少个元素

3.data_type代表元素的data_type 待存储元素的类型 才能开辟存储空间

4.类型:每个存储的元素是任意类型 一个顺序表中的元素是可以一个顺序表:二维顺序表

5.每次结构操作对哪些变量产生影响

5.1插入 改变length++ size做扩容操作时会变 所有元素后移 O(n)

insert(loc,value)将value的值插入到下标为loc的位置

(1)判断位置是否合法

(2)判断存储空间是否已满

(3) 将插入位置之后的元素全部后移

(4)将插入的元素放到插入位置

5.2删除 改变length-- 所有元素前移

remove(index)移除下标为index的元素 O(n)

(1)判断index是否合法 index是否在length内

(2)将index之后元素都前移一位

(3)改变length长度

5.3扩容

expand() O(n)

(1)将所有元素放到临时的存储空间里

(2)将原来的存储空间扩大

(3)将元素从临时的存储空间放到新的存储空间里

(4) 释放原来的存储空间

5.4查找

search(value)寻找顺序表中值为value的函数 O(n)

(1)从下标为0的地方开始遍历

(2)发现和目标元素相等时返回下标值

(3)未找到返回-1

5.5输出

print()输出顺序表中所有元素 O(n)

(1)从下标为零开始遍历顺序表

(2)将每一位元素输出

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

typedef struct Vector{
	int *data; //int类型的顺序表,一个指针指向一片连续的存储空间;
	int size,length;
}Vector;

void *init(int n){//init返回一个顺序表的地址,init初始化一个有n个空间的顺序表;
	n = 1;
	Vector *ret = (Vector *)malloc(sizeof(Vector) * 1);
	//to do something for init Vector;
	ret->data = (int *)malloc(sizeof(int) * n);
	ret->size = n;
	ret->length = 0;
	return ret;
}

int expand(Vector *vec){
	int *p = (int *)realloc(vec->data,vec->size * 2);//如果不用p暂存的话,有可能申请失败造成内存泄漏;
	if(p == NULL) return 0;
	vec->data = p;
	vec->size *= 2;//新的顺序表变大了;
	return 1;
}

int insert(Vector *vec, int val, int ind){
	if(vec == NULL) return 0; //如果顺序表为空就不能进行插入操作;
	if(ind > vec->length || ind < 0) return 0;//判断合法条件,大于或小于所在length之内的都不能进行插入操作;
	if(vec->length == vec->size && !expand(vec)) return 0;
	//顺序表满了;
	//将后面的元素向后平行一位,从后向前扫,防止覆盖丢数据;
	for(int i = vec->length; i > ind; i--){
		vec->data[i] = vec->data[i - 1];
	}
	vec->data[ind] = val;//更改结构体里面的值;
	vec->length += 1;
	return 1;
}

int delete_node(Vector *vec, int ind){
	if(vec == NULL) return 0;
	if(ind < 0 || ind >= vec->length) return 0;
	//从ind向后扫,所有元素向前移动一位;
	for(int i = ind + 1; i < vec->length; i++){
		vec->data[i - 1] = vec->data[i];
	}
	vec->length -= 1;//更改结构体里面的值;
	return 1;
}

void clear(Vector *vec){
	if(vec == NULL) return ;
	if(vec->data != NULL) {free(vec->data);}
	free(vec);
	return ;
}

void output(Vector *vec){
	printf("Vector(%d %d) : [", vec->length, vec->size);
	for(int i = 0; i < vec->length; i++){
		printf(" %d", vec->data[i]);
	}
	printf(" ]\n");
}

int main(){
	int n = 1;
	Vector *vec = init(n);
	int val, ind;
	while(~scanf("%d%d", &val, &ind)){
		printf("insert %d %d : %d\n", val, ind, insert(vec, val, ind));
		output(vec);
	}
	clear(vec);
	return 0;
}	
输出结果:
1 0
insert 1 0 : 1
Vector(1 1) : [ 1 ]
2 1
insert 2 1 : 1
Vector(2 2) : [ 1 2 ]
3 2
insert 3 2 : 1
Vector(3 4) : [ 1 2 3 ]

三:链表

程序内部:在程序内部有一个变量记录着内存的首地址,也就是head

内存内部:数据域和指针域

链表的稳定排序是冒泡排序 较有序时可以比较少 它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。

选择排序的时间稳定在n*(n - 1)/2 难以优化 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

单项循环链表
所指向的节点是最后一个节点,把head看作整个循环链表的为节点

fflush(sdout)

1.1插入

回文链表

求链表的长度

截断链表

将后面的链表逆序

判断两个链表是否相等

链表反转 虚拟节点 把原链表的所有节点依次插入新链表的头部

增加虚拟节点

insert(node,index) 将node插入到链表下标为index的位置 O(n)

(1)找到待插入节点的位置 找到最后还未找到待插入节点的位置 则为非法的 返回false (找到待插入节点的前一个节点)

(2)将插入节点的next指针指向插入位置的当前节点

(3)将插入节点的前一个指针指向待插入节点

(2)与(3)不能倒序 会出现内存泄漏的问题 后面节点会访问不到

1.1输出

output() O(n)

(1)定义一个用于遍历的变量,初始指向头节点;

(2)输出当前节点的值,将遍历变量的节点后移一位;

(3)如果未遍历到最后的节点,则重复2操作;直到遍历到最后节点

删除

delete_node(index)O(n)

\1. 从表头遍历找到要删除的位置。

\2. 令删除位置前一个结点的next指针指向待删除位置后一个结点。

\3. 删除结点。

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

typedef struct LinkNode{//结构定义;
	int data;
	struct LinkNode *next;//不可以把struct去掉,因为在里面的时候还没有成为一个新的结构体;
}LinkNode, *Linkedlist;//指针类型为Linkedlist;

LinkNode *get_node(int val){//初始化一个链表节点,数据域等于val;指针域默认为NULL;
	LinkNode *p = (LinkNode *)malloc(sizeof(LinkNode) * 1);
	p->data = val;
	p->next = NULL;
	return p;
}

//链表插入可能改变头地址LinkNode和Linkedlist是一样的,返回LinkNode * 和返回Linkedlist是一样的;
Linkedlist insert(Linkedlist head, int val, int ind){
	if(ind < 0) return head;
	LinkNode ret, *p;//增加头部虚拟结点,使插入操作十分方便;
	ret.next = head;
	p = &ret;//指针p指向整个链表的头节点的前一个节点,如果ind=0,就走0步找到前一个结点,ind=m,就走m步;
	while(ind != 0 && p != NULL){
		ind--, p = p->next;//循环遍历直到找到待插入结点的前一个结点;
	}
	if(p == NULL) return head;//p为NULL时,就是插入失败;
	LinkNode *new_node = get_node(val);
	new_node->next = p->next;
	p->next = new_node;
	return ret.next;//ind=0时可能改变链表的头地址,返回值就不是head,修改p->next,修改的就是ret.next;
}

Linkedlist delete_node(Linkedlist head, int ind){
	if(ind < 0) return head;
	LinkNode ret, *p;
	ret.next = head;
	p = &ret;
	while(ind != 0 && p->next != NULL){//要保证待删除结点p->next不为空;
		ind--, p = p->next;//p为循环遍历到待删除结点的前一个结点;
	}
	if(p->next == NULL) return head;//跳出while循环,就判断为空就不合理,返回head;
	LinkNode *node = p->next;//防止内存泄漏,保存待删除结点;
	p->next = p->next->next;
	free(node);
	return ret.next;//局部变量,出了作用域ret就被释放了;
}

void clear(Linkedlist head){
	LinkNode *p = head, *q;
	while(p != NULL){
		q = p;//q来保存待删除结点;
		p = p->next;//待删除结点后移;
		free(q);
	}
	return ;
}

void output(Linkedlist head){
	LinkNode *p = head;
	while(p != NULL){
		printf("%d-> ",p->data);
		p = p->next;
	}
	printf("null\n");
	return ; 
}

int main(){
	Linkedlist head = NULL;
	int op, val, ind;
	while(~scanf("%d", &op)){
		switch(op){
			case 1 :
				scanf("%d%d", &val, &ind);
				head = insert(head, val, ind);
				printf("insert %d at %d\n", val, ind);
				break;//case=1时,表示插入操作;
			case 2 :
				scanf("%d", &ind);
				head = delete_node(head, ind);
				printf("delete %d element\n", ind);
				break;//case=2时,表示删除操作;
			default :
				fprintf(stderr, "input error\n");
				break;//op等于其他值的话,就给一个提示信息,打到标准错误输出,“你输入错了”;
		}
		output(head);
	}
	clear(head);
	return 0;
}

输出结果:

1 1 0
insert 1 at 0
1-> null
1 2 3
insert 2 at 3
1-> null
1 3 1
insert 3 at 1
1-> 3-> null
1 4 0
insert 4 at 0
4-> 1-> 3-> null
1 5 2
insert 5 at 2
4-> 1-> 5-> 3-> null
2 2
delete 2 element
4-> 1-> 3-> null
2 0
delete 0 element
1-> 3-> null
2 1
delete 1 element
1-> null
2 0
delete 0 element
null
2 0
delete 0 element
null

链表的翻转

reverse()

\1. 定义一个用于遍历的指针,初始指向头结点后一个结点。

\2. 让头结点的 next 指针置空。

\3. 从当前遍历指针所指的结点开始遍历链表,将遍历到的结点 next 指针指向头结点。遍历过程中借助另外一个指针保存下一个遍历到的结点。

\4. 重复步骤 3 直至表尾,此时新的链表就是原链表反转后的链表。

约瑟夫问题的实现

\1. 定义一个遍历指针,初始指向 head,并让 head 指向空地址避免操作结束后变为野指针。

\2. 找到遍历指针往后数的第 n 次所指向的结点。

\3. 输出该结点的数据,更新遍历指针,然后删除该结点。

\4. 重复操作 2 直至只剩下最后一个结点。

\5. 输出最后一个结点并删除。

其他

malloc 只开辟一个空间,不进行初始化

kalloc 开辟的空间初始化为0

realloc 大小不够,重新开辟 realloc( , )第一个参数值为原来空间的第一位置,第二个参数值为想更新空间为多大;如果realloc成功了,那么就将原数据拷到新的地址段去,但是存储空间增大了;新的存储空间可能和原来的存储空间是一样的,


猜你喜欢

转载自blog.csdn.net/qq_38362049/article/details/81034944