总结
第一章:
数据结构包括:逻辑结构,储存结构, 运算集合
逻辑结构:分为线性(线性表, 栈, 队列, 字符串, 数组, 广义表)
非线性:树,图,网
储存结构:顺序储存和非顺序储存 线性储存,散列储存,链式储存
算法+数据结构 = 程序
第二章:线性表:
线性表的线性储存
逻辑结构:
除了端点以外,所有元素均有一个前驱和一个后继.具有一对一的关系.
存储结构:
一组地址连续的储存单元依次存储
#define MAX 100
typedef struct{
int elem[MAX];
int last;
}SeqList;
基本运算:
查找:
按照坐标查找和按照内容查找
插入:
判插入位置,判满-->找到位置,移动后面的元素-->插入!
删除:
参数:删除的位置序号
判位置-->将插入元素放到对应变量中-->移动元素
双非递减有序表合并:
元素比较插入-->单表剩余元素插入
2.3线性表的链式存储
单链表: 设头指针H指向第一个结点,前驱结点指针域存放下一个结点的地址
为了方便,在第一个结点之前设立头结点,头指针指向头结点,头结点的指针域存储指向第一个结点的指针
typedef struct Node{//Node为结构标记
char data;
struct Node *next;
}Node, *LinkList; //Node为类型定义
注意:Node*和LinkList同为结构指针, 两者等价.后者习惯指单链表头指针变量,前者定义单链表中结点指针
Attention!!!!!!!
初始化:
InitList(LinkList *L){
//该处为双指针, 返回值为空, 靠指针传出去!!!
*L = (LinkList)malloc(szieof(Node));
(*L)->next = NULL;
}
//L指向单链表头结点的指针,*L初始化单链表的头指针变量
建立单链表://头插法
void CreateFromHead(LinkList L){
Node *s;
char c;
int flag = 1;
while(flag){
c = getchar();
if(c != '$'){
s = (Node *)malloc(sizeof(Node));
s->data = c;
s->next = L->next;//L相当于头结点(表头指针)
L->next = s;
}
else
flag = 0;
}
}
//尾插法
void CreateFromTail(LinkList L){
Node *s, *r;
char c;
int flag = 1;
r = L;
while(flag){
c = getchar();
if(c != '$'){
s = (Node *)malloc(sizeof(Node));
s->data = c;
r->next = s;
r = s;
}
else{
flag = 0;
r->next = NULL;
}
}
}
查找:
按照序号和按照值,和线性表差不多,判空条件改为while(p)
求长度:...
单链表插入:
判断插入位置合理性-->插入(一般都是头插,类似语句如下:s->next = pre->next; pre->next = s)
删除:
关键语句:r = pre->next;//赋值
pre->next = r->next;
*e = r->data;
free(r);//!!!
合并两个有序单链表:
思想:不用新分配空间,直接比较数据修改指针,把其中的一个表头当作新表表头...
LinkList MergeList(LinkList LA, LinkList LB){
LinkList LC;
Node *pa, *pb, *r;
LC = LA;
pa = LA->next;
pb = LB->next;
r = LC;
while(pa && pb){
if(pa->data >= pb->data){
r->next = pa;
r = pa;
pa = pa->next;
}
else{
r->next = pb;
r = pb;
pb = pb->next;
}
}
if(pa)
r->next = pa;
else
r->next = pb;
r->next = NULL;
free(r);
free(LB);
return(LC); //return LC;
}
循环链表:将单链表最后一个结点指针域NULL改为指向表头结点
单循环链表判断条件: p != L或 p->next != NULL;
初始化循环单链表:
InitCLinkList(LinkList *CL){
*CL = (LinkList)malloc(sizeof(Node));
(*CL)->next = *CL;//注意CL是指针(地址), *CL为结点,(或者说结点指针, 就是一般的结点)!!!!!!!!!!!!
}
Attention!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
建立循环单链表:易错
void CreateCLinkList(LinkList CL){
//尾插法
Node *rear, *s;
char c;
rear = CL; //典型步骤
c = getchar();
while(c != '$'){
s = (Node *)malloc(sizeof(Node));
s->data = c;
rear->next = s;
rear = s;//典型
c = getchar();//!!别忘记了
}
rear->next = CL;//
}
循环单链表合并:
LinkList Merge(LinkList LA, LinkList LB){
Node *p, *q;//带头结点
p = LA;
q = LB;
while(p->next != LA) p = p->next;
while(q->next != LB) q = q->next;
p->next = LB->next;
q->next = LA;
free(LB);
return LA;
}
//稍难,带尾指针的带头结点循环内单链表合并算法
LinkList Merge_2(LinkList LA, LinkList LB){
//LA LB为尾指针,所以LA->next and LB->next 为头结点
Node *p;
p = LA->next;//p暂时保存LA头结点地址
LA->next = LB->next->next;//指向LB第一个结点
free(LB->next);
LB->next = p;
return LB;//返回新的循环链表的尾指针
}
LinkList Merge_2(LinkList A, LinkList B){
Node *q;
q = B->next;
B->next = A->next->next;
free(A->next);
A->next = q;
return A;
}
双向链表:
typedef struct DNode{
int data;
struct DNode *prior, *next;
}DNode, *DoubleList;
插入:
//需要改变四个指针
int DLinkInsert(DoubleList L, int i, int e){
DNode *s, *p;
//判断插入位置是否合法
p = L;
for(; i > 0 && p; i--)
p = p->next;
//p最终指向待插入位置
if(!p) return FALSE;
s = (DNode *)malloc(sizeof(DNode));
s->data = e;
s->prior = p->prior;
p->prior->next = s;
s->next = p;
p->prior = s;
return OK;
}
删除相似:
判断插入位置-->找到删除节点->删除数据存入指针变量->改变指针->返回TRUE
关键步骤:
*e = p->data;
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);
静态链表:相比于动态链表(借助函数malloc和free),定义较大的结构数组,存放data和游标cursor
多次插入和删除造成链表假满,原因是未对删除元素空间进行回收.将所有删除和未分配的结点空间
通过游标链形成空闲结点链表...
线性表的应用:
一元多项式的表示及相加
typedef struct Polynode{
int coef;//系数
int exp;//指数
struct Polynode *next;
}Polynode, *Polylist;
尾插法建立:...
相加运算:
void Polyadd(Polylist polya, Polylist polyb){
Polynode *p, *q, *tail, *temp;
int sum;
p = polya->next;
q = polyb->next;//p,q分别指向第一个结点;
tail = p;
while(p && q){
if(p->exp < q->exp){
//尾插
tail->next = p;
tail = p;
p = p->next;
}
else if(p->exp > q->exp){
tail->next = q;
tail = q;
q = q->next;
}
else{
//指数相等,判断系数之和是否为零
//为零,分别走下一个结点,同时释放p,q;不为零, ....
sum = p->coef + q->coef;
if(sum){
p->sum = sum;
tail->next = p;
tail = p;
p = p->next;
temp = q;
q = q->next;
free(temp);
}
else{
temp = p;
p = p->next;
free(temp);
temp = q;
q = q->next;
free(q);
}
}
}
//值得注意的是,也许其中一个链表数据并没有走完
if(p)
tail->next = p;
else
tail->next = q;
}
经典算法:
删除顺序表中所有值为x的元素,时间复杂度尽可能小
思想:避免直接扫描-->删除-->移动...
在原来表的基础上构造新表,方法:若是不等于x,那么直接复制到新表;等于就跳过.注意的是新表不用另外声明.
单链表第一个存储元素的值划分单链表为两半, 小于放该元素前面,大于相反
//默认有头结点
void divide_2(LinkList L){
if(!L->next) return;
Node *p, *q, *pre, *s;
s = L->next;//r指向划分结点
pre = s;
p = s->next;
while(p){
//p!=NULL
q = p->next;//指针跟踪技术
if(p->data < s->data){
//头插,利用pre的指针跟踪
pre->next = p->next;//pre后继连接到q
//头插
p->next = L->next;
L->next = p;
p = q;
}
}
第二章总结:
存储密度:顺序表为百分百,链表会低很多
优缺点:顺序表为随机存储,单链表需要从头查找.插入删除少就顺序表
线性链表分为:
带头结点的单链表!!!很重要!:特点只能从前往往后寻找
带头结点的循环单链表:寻找一个结点的前驱时间耗费O(n);
带尾指针的循环单链表:寻找尾结点和头结点方便
带头结点的双向循环链表:前驱和后继都好找但是储存密度相对较低.
第三章 限定性线性表--栈和队列!必考!!!
栈:单向插入和删除,叫做入栈和出栈 后进先出的特点
定义;
typedef struct {
int elem[MAX];
int top;//空为-1
}SeqStack;//顺序栈
初始化:栈顶置为-1
进栈:判满-->栈顶++ ->进栈
出栈:判空-->栈底-- ->出栈
!!
//判空
bool IsEmpty(SeqStack *S){
if(S->top == -1)
return true;
else
return false;
}
//出栈
int Pop(SeqStack *S, int *e){
if(IsEmpty(S))
return -1;
*e = S->elem[S->top];
S->top--;
return 1;
}
多栈共享技术:双端栈,栈底分别为两端,判满:S->top[0] + 1 == S->top[1];判空: S->top[0] == 0,S->top[1] == MAX-1;
链栈:
定义:
typedef struct node{
int data;
struct node *next;
}LinkStackNode;
typedef LinkStackNode* LinkStack
//进栈:top为头结点
int Push(LinkStack top, int x){
LinkStackNode *temp;
temp = (LinkStackNode *)malloc(sizeof(LinkStackNode));
if(temp == NULL){
printf("申请空间失败\n");
return FALSE;
}
temp->next = x;
temp->next = top->next;
top->next = temp;
return TRUE;
}
//出栈
int Pop(LinkStack top,int *x){
LinkStackNode *temp;
temp = top->next;
if(temp == NULL){
return FALSE;
}
top->next = temp->next;
*x = temp->data;
free(temp);
return TRUE;
}
//多栈运算 略
#define N 10
typedef struct node{
int data;
struct node *next;
}LinkStackNode, *LinkStack;
LinkStack top[N];
//top[i]分别是十个栈中每个栈的栈顶
//括号匹配
void BracketMatch(char *str){
Stack S;
int i;
char ch;
//初始化
InitStack(&S);
for(i = 0; str[i] != '\0'; i++){
switch(str[i]){
case '[':
case '{':
case '(':
Push(&S, str[i]);
break;
case ')':
case ']':
case '}':
if(IsEmpty(&S)){
printf("右括号多余!\n");
break;
}
else{
GetTop(&S, &ch);
if(Match(ch, str[i])){
Pop(&S, &ch);
}
else{
printf("括号不匹配\n");
return;
}
}
}
}
if(IsEmpty(&S)){
printf("括号匹配成功!\n");
return;
}
else
printf("左括号多余\n");
}
//表达式求值 略
int ExpEvaluation(){
InitStack(&OPTR);//运算符栈
InitStack(&OVS);//运算数栈
Push(&OPTR, '#');
printf("\n\nPlease enter an expression(End with '#'):");
ch = getchar();
while(ch == '#' || GetTop(OPTR)!='#'){
if(!In(ch, OPSet)){
n = GetNumber(ch);
Push(&OVS, n);
ch = getchar();
}
else{
switch(Compare(ch, GetTop(OTPR))){
case '>':Push(&OPTR, ch);
ch = getchar();
break;
case '=':
case '<':
Pop(&OPTR, &op);
Pop(&OVS, &a);
Pop(&OVS, &b);
v = Execute(a, op, b);
Push(&OVS, v);
break;
}
}
}
v = GetTop(OVS);
return (v);
}
栈与递归:
常见:斐波那契数列, 汉诺塔问题, 阶乘...
阶乘递归:
int f(int n){
if(n == 0)
return 1;
else
return n * f(n-1);
}
斐波那契:
Fib(int n){
if(n == 0 || n == 1)
return n;
else
return Fib(n-1) + Fib(n-2);
}
队列:一端进去,另一端出来 先进先出.
!!!插入一端为队尾, 出去一端为队头,所以插入用尾插!!!!!!!!!!!!!!!!!!!!!!!!!!
链队列:
定义:
typedef struct Node{
int data;
struct Node *next;
}LQNode;
typedef struct{
LQNode *front;
LQNode *rear;
}LQ;//LinkQueue
初始化:
int InitQueue(LQ *Q){
Q->front = (LQNode *)malloc(sizeof(Node));
if(Q->front){
Q->rear = Q->front;
Q->front->next = NULL;
return 1;
}
return -1;
}
入队://这个不太好理解,需要画图
int EnterQueue(LQ *Q, int x){
LQNode *new_node;
new_node = (LQNode *)malloc(sizeof(LQNode));
if(!new_node) return -1;
new_node->data = x;
new_node->next = NULL;
Q->rear->next = new_node;//尾插
Q->rear = new_node;
return 1;
}
循环队列:....
//辗转相除法和排队等待问题
int fun(int m, int n){
int r;
if(n > m){
return fun(n, m);
//相与交换位置
//多次辗转相除找出最大公约数
}
else if(n == 0){
return m;
}
else{
r = m % n;
return (fun(n, r));
}
}
int fun(int m, int n){
int r;
do{
r = m % n//分母较大就相当于执行交换值的操作;
m = n;
n = r;
}while(r != 0);
return m;
}
void seedoctor(){
InitQueue(&Q);
flag = 1;
while(flag){
printf("Please enter the code:");
ch = getch();
switch(ch){
case 'a':
printf("\n病历号:");
scanf("%d", &n);
EnterQueue(&Q, n);
break;
case 'n':
if(IsEmpty){
DeleteQueue(&Q, &n);
printf("病历号为%d号的就诊\n", n);
}
else
printf("无病人等待");
break
case 'q':
printf("今天停止挂号, 以下病人依次就诊.");
while(!IsEmpty){
DeleteQueue(&Q, &n);
printf("%d \n", n);
}
flag = 0;
break;
default:
printf("输入错误.\n");
}
}
}
第三章总结:
栈:先进后出; 顺序栈可能产生上溢,链栈不会.
队列:先进先出; 链队列设头指针和尾指针,封装在一个结构体里面.
第四章:串
字符串:双引号括起来.....空串:就是一对引号
定长顺序串:静态结构, 地址连续存储单元存储字符串序列
定义:
typedef struct{
char ch[MAX];
int len;
}SString;
插入:插入位置position会将已经有的串L分为两个部分i,j,新插入的串s会导致以下三种情况
1. 插入后仍然长度小于MAX
2. 插入后j串溢出一部分
3. 插入后j串全部溢出, s溢出一部分
int SStringInsert(SString *s, int pos, SString t){
int i;
//判插入位置合理性
if(pos < 0 || pos > s->len) return -1;
//第一种情况, 先将后串后移,再插入
if(s->len + t.len <= MAX){//注意的是一个是类似字符串指针, 一个用点运算符
//后移
for(i = pos + t.len; i < t.len + s->len; i++)
s[i] = s[i-t.len];
//复制
for(i = pos; i < pos + t.len; i++)
s[i] = t.ch[i-pos];
s->len += t.len;
}
else if(pos + t.len <= MAX){
for(i = MAX - 1; i > pos; i--)
s[i] = s[i - t.len];
for(i = pos; i < pos + t.len; i++)
s[i] = t.ch[i-pos];
s->len = MAX - 1;
}
else{
for(i = pos; i < MAX; i++)
s[i] = t.ch[i-pos];
s->len = MAX - 1;
}
return 1;//#define OK 1; return OK;
}
//Delete
int StrDelete(SString *s, int pos, int len){
//从串中删除从pos位置起的len长度的字符
int i;
if(pos < 0 ||pos > (s->len-len)) return 0;
for(i = pos + len; i < s->len; i++)
s->ch[i-len] = s->ch[i];
s->len -= len;
return 1;
}
串比较
//Compare
int StrCompare(SString s, SString t){
int i=0;
for(i = 0; i< s.len && i < t.len; i++)
if(s.ch[i] != t.ch[i])
return (s.ch[i] - t.ch[i]);
return s->len-t.len;//注意返回值
//如果前者大于后者, 第一个return返回正;后者大, 返回负数
//前面能够完全匹配, 那么第二个return比较字符串长度
}
最基础的BF算法
//定位函数(布鲁特-福斯算法)
int StrIndex(SString s,int pos, SString t){
int i, j ,start;
//对s字符串一轮一轮的从pos字符开始和t字符串比较, 比较不成功则start向后移
start = pos;
i = start;
j = 0;
while(i < s.len && j <t.len){
if(s.ch[i] == t.ch[j]){
i++;
j++;
}
else{
start++;
i = start;
j = 0;
}
}
if(j >= t.len) return start;
else return -1;
}
堆串:
定义:
typedef struct{
char *ch;
int len;
}HString;
存储方式有效率, 但是程序执行过程中会不断的销毁和生成新串!!!
//插入
int StrInsert(HString *s, int pos, HString *t){
int i;
char *p;
if(pos < 0 || pos > s->len || s->len == 0) return 0;
//为新串分配空间
p = (char *)malloc(sizeof(s->len + t->len));
if(!p) return 0;
//旧的前部分复制到新
for(i = 0; i < pos; i++)
p[i] = s->ch[i];
//复制t
for(i = pos; i< pos +t->len; i++)
p[i] = t->ch[i-pos];
//复制旧的串后部分
for(i = pos; i<s->len;i++ )
p[i+s->len] = s->ch[i];
s->len += t->len;
!!!!!!!!!!!!!!!!!!!!!!!!!下一步!!!!!!!!!!!!!!!!!!!!
字符串指针直接复制, 不是普通数组不用strcpy()
s->ch = p;
return 1;
}
//赋值
int StrAssign(HString *s, char *tval){
int len, i = 0;
if(s->len == 0)return 0;
while(tval[i] != '\0') i++;
//得到tval字符串的长度
len = i;
if(len){
s->ch = (char *)malloc(sizeof(len));
if(!s->ch) return 0;
for(i = 0; i< len; i++)
s->ch[i] = tval[i];
s->len = len;
return 1;
}
else
s->ch = NULL;
return 1;
}
//块链串
#define MAX 4
typedef struct Block{
char ch[MAX];//data域, 结点大小, 指的是存放字符的个数
struct Block *next;//链域, 指的是link域占用字符个数
}Block;
//储存密度 data占用存储位/实际为串分配存储位 存储密度越小运算处理越方便
//MAX == 1时, 就是一个普通的线性链表
typedef struct{
Block *head;
Block *tail;
int len;
}BLString;
icoding例题改编测试!!!!
#include <stdlib.h>
#include <stdio.h>
#define BLOCK_SIZE 4
#define BLS_BLANK '#' // 用于空白处的补齐字符!!!!!!!
typedef struct _block {
char ch[BLOCK_SIZE]; //块的数据域
struct _block *next; //块的指针域
} Block;
typedef struct {
Block *head; // 串的头指针
Block *tail; // 串的尾指针
int len; // 串的当前长度
} BLString;
//字符串初始化函数:
void blstr_init(BLString *T) {
T->len = 0;
T->head = NULL;
T->tail = NULL;
}
//这些定义已包含在头文件 dsstring.h 中,请实现块链串的子串查找操作:
//
//bool blstr_substr(BLString src, int pos, int len, BLString *sub);
//src为要查找的字符串
//pos为子串开始的下标
//len为子串的长度
//sub在函数调用运行前指向一个已经初始化好的空串,在函数返回时,sub指向串src从第pos个字符起长度为len的子串
//函数查找成功返回true,参数不正确返回 false
#include "dsstring.h"
#include <stdio.h>
#include <stdlib.h>
//函数声明:长度判断很多写法,大多数不同的编译器都会各种报错,这种不是最简便的但是一般不会报错
int len(const char* s)
{
int q = 0;
while (*s != '\0') {//while(*s)
q++;
s++;
}
return q;
}
int StrAssign(BLString* S, const char* cstr)
{
//将串cstr存入S中去, S是块链串
int i, j, k, len;
Block *p, *q;
len = strlen(cstr); /*len为链串的长度 */
if (len == 0)
return 0;
S->len = len;
//整除哈!! 8 / 5 = 1
j = len / BLOCK_SIZE; /*j为链串的结点数 */
if (len % BLOCK_SIZE)
j++;
for (i = 0; i < j; i++) {
p = (Block*)malloc(sizeof(Block)); /*动态生成一个结点*/
if (!p)
return 0;
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!字符串重点
for (k = 0; k < BLOCK_SIZE && *cstr; k++) /*将字符串ctrs中的字符赋值给链串的数据域*/
*(p->ch + k) = *cstr++;//p->ch[k] = cstr[xxx]
//指针跟踪
if (i == 0) /*如果是第一个结点*/
S->head = q = p; /*头指针指向第一个结点*/
else {//尾插,q为尾结点
q->next = p;
q = p;
}
if (!*cstr) /*如果是最后一个链结点*/
{
S->tail = q; /*将尾指针指向最后一个结点*/
q->next = NULL; /*将尾指针的指针域置为空*/
for (; k < BLOCK_SIZE; k++) /*将最后一个结点用'#'填充*/
*(q->ch + k) = BLS_BLANK;
}
}
return 1;
}
bool blstr_substr(BLString src, int pos, int len, BLString* sub)
{
char* t;
if (pos < 0 || pos >= src.len || len < 1)
return false;
int n = pos / BLOCK_SIZE, h = pos % BLOCK_SIZE;
Block* temp = src.head;
for (int i = 0; i < n; i++) //假装到初始节点
{
temp = temp->next;
}
char str[100];
int i = 0;
while (i < len) {
if (h >= BLOCK_SIZE) {
temp = temp->next;
h = 0;
} else {
if (!temp || temp->ch[h] == BLS_BLANK)
break;
str[i++] = temp->ch[h++];
}
}
str[i] = '\0';
StrAssign(sub, str);
return true;
}
//算法BF and KMP
略过........
//BF 回溯算法
int StrIndex(SString s, int pos, SString t){
int i, j;
if(t.len == 0)
return (0);//空串任意串的字串
i = pos;
j = 0;
while(i < s.len && j < t.len){
if(s[i] == t[j]){
i++; j++;
}
else{
i = i-j+1; //关键
j = 0;
}
}
if(j >= t.len)
return (i-j);
else
return -1;
}
//KMP
//next
void GetNext(SString t, int next[]){
int j, k;
j = 0;
k = -1;
next[0] = -1;
while(j < t.len-1){
if(k == -1|| t.data[j] == t.data[k]){
j++;k++; next[j] = k;
}
else{
k = next[k];
}
}
}
//KMP
int StrIndex_KMP(SString s, int pos, SString t, int next[]){
int i, j;
if(t.len == 0) return 0;
i = pos;
j = 0;
while(i<s.len && j<t.len){
if(j == -1 || t.data[i] == s.data[j]){
i++;j++;
}
else
j = next[j];
}
if(j>=t.len)
return i-t.len;
else
return -1;
}
第四章总结:
字符串是特殊的线性表, 元素为字符.
区分:定长顺序串(静态储存),堆串(多次分配销毁),块链串(链表)