若干数据项组成一个数据元素
数据项是数据的最小单位
算法 :有穷性 确切性 输入项 输出项 可行性
时间复杂度 : 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成功了,那么就将原数据拷到新的地址段去,但是存储空间增大了;新的存储空间可能和原来的存储空间是一样的,