Algorithms (tutorialhorizon.com)
线性表
线性表定义
线性表的特点
- 有序性
- 有穷性,由有限个数据元素组成,表长就是表中数据元素的个数
- 同一性。数据元素都是同一数据类型
顺序表
用一组地址连续的存储单元顺序存放线性表的各个数据元素(顺序存储结构)。
#include <iostream>
#define MAXSIZE 100
typedef int ElemType;
typedef struct {
ElemType data[MAXSIZE];
int length; //length+1为表长
}seqList;
// 1 <= i <= list.length
int getElem(seqList* L, int i, ElemType* e) {
if(L->length == 0 || i < 1 || i > L->length)
return 0;
*e = L->data[i - 1];
return 1;
}
int insert_list(seqList* L, int i, ElemType e) {
int k;
//顺序表已满
if(L->length == MAXSIZE)
return 0;
//当i不在范围内
if(i < 1 || i > L->length + 1)
return 0;
// 若插入位置不在表尾
if(i <= L->length) {
//将要插入位置后的数据元素后移一位
for(k = L->length - 1; k >= i - 1; k--) {
L->data[k + 1] = L->data[k];
}
}
L->data[i-1] = e; //将新元素插入
L->length++; //表长增1
return 1;
}
int delete_list(seqList* L, int i, ElemType* e) {
int k;
//线性表为空
if(L->length == 0)
return 0;
//删除位置不正确
if(i < 1 || i > L->length)
return 0;
*e = L->data[i-1];
//如果删除的元素不在最后位置
if(i < L->length) {
//将删除位置后继元素前移
for(k = i; k < L->length; k++) {
L->data[k-1] = L->data[k];
}
}
L->length--;
return 1;
}
链表
单链表
#include <stdio.h>
//抽象数据类型
typedef int Data;
typedef struct _Node { //结点
Data data;
struct _Node* next;
}Node;
typedef struct _LinkList {
Node* front; //指向头结点的指针
Node* tail; //指向尾结点的指针
int size; //链表的大小
}LinkList;
//创建结点
Node* createNode(Data data) {
//1. 动态初始化新节点
//2. 判断新节点是否有足够的内存空间
//3. 如果新节点有足够的内存空间,就令新结点的next指向空, 将数据赋值在新节点上
Node* newNode = (Node*)malloc(sizeof(Node));
if(newNode != NULL)
{
newNode->next = NULL;
newNode->data = data;
}
return newNode;
}
//判断链表是否为空
bool empty(LinkList* list) {
return list->size == 0;
}
//创建链表
LinkList* createList() {
//1.动态分配
LinkList* list = (LinkList*)malloc(sizeof(LinkList));
//2.如果list为空
if(list == NULL)
return NULL;
//3.首尾指针指向NULL
list->front = NULL;
list->tail = NULL;
//4.链表元素初始化为0
list->size = 0;
return list;
}
//插尾
void push_tail(LinkList* list, Data data) {
Node* newNode = createNode(data);
if(newNode == NULL)
return;
if(empty(list)) {
list->front = newNode;
} else {
//让尾节点连上新节点
list->tail->next = newNode;
//让尾指针指向新节点
}
list->tail = newNode;
list->size++;
}
//头插法
void push_front(LinkList* list, Data data) {
Node* newNode = createNode(data);
if(newNode == NULL)
return;
//如果链表为空
if(empty(list)) {
list->front = newNode;
list->tail = newNode;
} else {
newNode->next = list->front;
list->front = newNode;
}
list->size++;
}
//输出、查找、删除要遍历
//给用户提供一个接口
void transform(LinkList* list, void (*func)(Data*)) {
Node* curNode = list->front;
while(curNode != NULL) {
func(&curNode->data);
curNode = curNode->next;
}
}
//输出链表
void traverse(LinkList* list) {
//1. 如果链表为空,就退出程序
//2. 链表不为空,就定义一个指针curNode,让它指向头结点
//3. 只要指针curNode不指向NULL, 就打印当结点的值
//4. 让curNode不断往后移
if(empty(list))
return;
Node* curNode = list->front;
while(curNode != NULL) {
printf("%d ", curNode->data);
curNode = curNode->next;
}
}
void print(Data* data) {
printf("%d ", *data * 2);
}
int main()
{
LinkList* list = createList();
for(int i = 0; i < 5; i++) {
push_tail(list, i);
}
transform(list, print);
return 0;
}
在链表中删除值为x的元素
- 定义两个指针变量, prev(前驱结点)和find(值为x的结点,要查找的结点)
- 令find = 头结点的下一个结点,也就是说从第二个结点开始找
- 定义一个循环,条件:查找的结点的值 不等于 x, 就让2个指针同时移动
- 如果x的值刚好等于find结点的值, 就令前驱结点的后继结点等于find结点的后继结点
Node* delete_list(Node* head, int x) {
Node* prev; //前驱结点
Node* find; //要查找的结点
find = head->next;
//查找值为x的元素
while(find->data != x) {
//让2个指针同时移动
prev = find;
find = find->next;
}
find->next = prev->next;
delete find;
return head;
}
求带头结点链表的表长
- 定义一个指针变量cur
- 初始化整型变量i为0
- 令cur指向链表的头结点
- 循环 条件:cur不为空 操作: 令i自增,cur指针往后移
int Length_List(Node* head) {
Node* cur = head;
int i = 0;
while(cur != NULL) {
i++;
cur = cur->next;
}
return i;
}
在单链表list中查找第i个元素的结点(按序号查找单链表数据元素)
- 定义一个指针变量 cur
- 初始化整型变量j为1
- 令cur指向list中的第二个结点,从第二个结点开始找
- 循环 条件:cur不为空且 j < i 操作:cur往后移,j累加
- 返回cur
Node* search(Node* list, int i) {
Node* cur = list->next;
int j = 1;
while(cur != NULL && j < i) {
cur = cur->next;
j++;
}
return cur;
}
在单链表list中查找值为x的结点(按值查找单链表数据元素)
- 定义一个指针变量cur
- 让cur指向list的第二个结点
- 循环 条件:cur不为空且cur指向的结点的值不等于x 操作:cur往后移
- 返回cur
Node* search(Node* list, int x) {
Node* cur = list->next;
while(cur != NULL && cur->data != x) {
cur = cur->next;
}
return cur;
}
单链表中删除相同的值 解题思路:先取头结点的值,将它与后面的所有结点值一一比较,发现相同的就删除掉,然后再取第二个结点的值,重复上述过程直到最后一个结点
解题步骤:
-
定义三个指针变量, *p, *q, *r
-
让指针p指向头结点
-
外层循环 条件(指针p不为空)
操作: 将指针p所指向的地址赋值给q 内层循环 条件(指针q的后继结点不为空) 操作: 条件(指针q的后继结点的数据与指针p结点的数据相同) 指针r指向q的后继结点的数据 q的后继结点指向 r的后继节点 删除r(删除重复结点) 否则 q往后走内层循环结束p往后走 外层循环结束
Node* p = NULL;
Node* q = NULL;
Node* r = NULL;
p = head;
while(p != NULL) {
q = p;
while(q->next != NULL) {
if(q->next->data == p->data) {
r = q->next;
q->next = r->next;
delete r;
} else
q = q->next;
}
p = p->next;
}
}
双向链表
插头
- 动态分配新节点newNode
- 检查溢出条件,即判断内存是否可用于新结点, 内存不可用,显示溢出消息,可用,就为新节点分配内存
- 为新节点分配数据
- 判断双向链表是否为空
4.1: 如果双向链表为空(无结点),令newNode成为头结点, 且newNode的前驱和后继指针指向NULL
4.2: 如果双向链表不为空, 令新结点的后继指针指向head,head的前驱指针指向新节点,最后令新节点成为头结点。
#include <iostream>
typedef struct _Node {
int data;
struct _Node* next;
struct _Node* prev;
}Node;
void insert_front(Node** head, int data) {
//先动态分配一个新节点
Node* newNode = (Node*)malloc(sizeof(Node));
//判断溢出条件
if(newNode == NULL) {
printf("Overflow");
return;
} else {
//内存分配给新结点
//赋值
newNode->data = data;
//判断双向链表是否为空
if(*head == NULL) {
//让新节点的前驱和后继指向NULL
newNode->prev = NULL;
newNode->next = NULL;
//让新节点成为头结点
*head = newNode;
} else {
//双向链表不为空
newNode->next = *head;
(*head)->prev = newNode;
newNode->prev = NULL;
*head = newNode;
}
}
}
//遍历双向链表
void traverse(Node* head) {
//判断双向链表是否为空
if(head == NULL) {
printf("空链表");
return;
}
Node* cur = head;
while(cur != NULL) {
printf("%d ", cur->data);
cur = cur->next;
}
}
插尾
- 动态分配新节点newNode
- 检查溢出条件,即判断内存是否可用于新结点, 内存不可用,显示溢出消息,可用,就为新节点分配内存
- 为新节点分配数据
- 判断双向链表是否为空
4.1: 如果双向链表为空(无结点),令newNode成为头结点, 且newNode的前驱和后继指针指向NULL
4.2: 如果双向链表不为空,定义一个指针变量lastNode, 通过循环遍历到最后一个结点(注意边界条件, lastNode->next != NULL),令lastNode的后继指针指向newNode, newNode的前驱指针指向lastNode, newNode的后继指针指向NULL
//先动态分配一个新节点
Node* newNode = (Node*)malloc(sizeof(Node));
//定义一个指针变量lastNode,用于遍历到最后一个结点
Node* lastNode = NULL;
//判断溢出条件
if(newNode == NULL) {
printf("Overflow");
return;
} else {
//内存分配给新结点
//赋值
newNode->data = data;
//判断双向链表是否为空
if(*head == NULL) {
//让新节点的前驱和后继指向NULL
newNode->prev = NULL;
newNode->next = NULL;
//让新节点成为头结点
*head = newNode;
} else {
//双向链表不为空
lastNode = *head;
// lastNode指向最后一个结点
while(lastNode->next != NULL) {
lastNode = lastNode->next;
}
newNode->prev = lastNode;
newNode->next = NULL;
lastNode->next = newNode;
}
}
}
删头
- 检查链表是否为空, 如果链表为空,打印underflow下溢条件
- 如果链表只有一个结点, 令头结点指向空,释放掉头结点
- 如果链表有多个结点,定义一个指针变量cur, 令cur存储指向第一个结点(即头结点)的地址, 头结点往后移,头结点的前驱指针指向NULL,最后释放掉cur所存储的结点的地址
Node* cur = *head;
if(*head == NULL) {
printf("Underflow");
return;
} else if((*head)->next == NULL && (*head)->prev == NULL) {
*head = NULL;
free(*head);
} else {
*head = (*head)->next;
(*head)->prev = NULL;
free(cur);
}
}
删尾
- 检查链表是否为空, 如果链表为空,打印underflow下溢条件
- 如果链表只有一个结点, 令头结点指向空,释放掉头结点
- 如果链表有多个结点,定义一个指针变量cur, 通过循环让cur指向最后一个结点的地址(待删除结点),令cur的前驱结点的后继指针指向NULL,cur指针指向NULL,最后释放掉cur, 就删除最后一个结点了
void delete_rear(Node** head) {
Node* cur = *head;
if(*head == NULL) {
printf("Underflow");
return;
} else if((*head)->next == NULL && (*head)->prev == NULL) {
*head = NULL;
free(*head);
} else {
//令cur指向最后一个结点
while(cur->next != NULL)
cur = cur->next;
cur->prev->next = NULL;
cur = NULL;
free(cur);
}
}
栈
栈具有后进先出的特点,限定元素的操作只能在表的一端(栈顶)进行。
栈和队列都有顺序或链式两种存储方式。
栈的主要操作是入栈和出栈。顺序栈由于受到期初设定的栈容量的限制,入栈操作时,必须进行判满操作,以免发生上溢。而链栈只有当无法动态申请到空间时,才无法入栈。
数组栈
#include <iostream>
#define MAXSIZE 100
typedef struct {
int data[MAXSIZE]; //栈中的元素通过数组来存储
int top;//栈顶指针
}seqStack;
//初始化栈
//1. 动态分配栈空间
//2. 如果栈空间分配不足,返回NULL
//3. 如果栈空间分配足,就让栈指针默认指向-1并且返回栈空间的地址
seqStack* init_seqStack() {
seqStack* s = new seqStack;
if(s == NULL) {
printf("栈空间分配不足");
return NULL;
} else {
s->top = -1;
return s;
}
}
//判断栈空,栈空返回1,栈不为空返回0
int empty(seqStack* s) {
if(s->top == -1)
return 1;
return 0;
}
//入栈,入栈前要考虑元素是否已满
//1. 判断栈是否已满,如果满了,返回0
//2. 如果栈没满,让栈顶指针往上移,并将元素赋值给指向栈顶指向的位置
//3. 入栈成功返回1, 入栈失败返回0
int push_seqStack(seqStack* s, int x) {
if(s->top == MAXSIZE - 1) {
printf("栈已满");
return 0;
}
s->top++;
s->data[s->top] = x;
return 1; //入栈成功
}
//出栈
//1. 先判断栈内元素是否为空,
// 如果栈里为空,返回0表示出栈失败
//2. 如果栈不为空
// 将栈顶元素值赋值给指针变量x
// 栈顶指针下移
// 返回1表示出栈成功
int pop_seqStack(seqStack* s, int* x) {
if(empty(s)) {
printf("栈空, 不能出栈");
return 0;
}
*x = s->data[s->top];
s->top--;
return 1;
}
//获取栈顶元素
//1.先判断栈内元素是否为空,如果为空 return 0
// 2.如果不为空,返回栈顶的元素
int getTop(seqStack* s) {
if(empty(s))
return 0;
return s->data[s->top];
}
int main()
{
return 0;
}
链栈
#include <iostream>
typedef struct Node {
int data;
struct Node* next;
}linkStack;
//初始化链栈
linkStack* init_linkStack() {
linkStack* top;
top = NULL;
return top; //返回栈顶为空指针
}
//判断栈空
int empty(linkStack* top) {
if(top == NULL)
return 1;
return 0;
}
//入栈
linkStack* push_linkStack(linkStack* top, int x) {
linkStack* s;
s = new linkStack; //动态分配,申请新的栈顶结点空间
s->data = x;
s->next = top; //原栈顶结点作为新节点的后继
top = s; //将新节点置为栈顶
return top;
}
//出栈
linkStack* pop_linkStack(linkStack* top, int* x) {
linkStack* p;
if(top == NULL)
return NULL; //栈空不能出栈,返回空指针
*x = top->data; //保存栈顶元素值
p = top;
top = top->next; //置新的栈顶指针
delete p; //释放原栈顶元素结点空间
return top; //出栈成功,返回新的栈顶指针
}
共享栈
两个栈的共享技术,也就是双端栈。主要利用了栈底位置不变,而栈顶位置动态变化的特性。首先申请一个共享的一维数组空间S[MaxSIZE]将两个栈的栈底分别放在一维数组的两端,分别是0和 MAXSIZE - 1。由于2个栈顶是动态变化的,所以各个栈可用的最大空间与实际使用的需求有关。
#define MAXSIZE 100
typedef struct {
int stack[MAXSIZE];
int top[2]; //两个栈顶指针
}DqStack;
//双端栈初始化
//1.初始化2个栈均为空栈
//2. s是指向栈类型的指针
void init_DqStack(DqStack* s) {
s->top[0] = -1;
s->top[1] = MAXSIZE;
}
//双端栈入栈, s是指向栈类型的指针,x是要入的值,k是栈的号数
// 1. 2个栈入栈前,都要判断栈是否已满
// 2.第一个栈入栈,因为第一个栈的栈顶为-1,所以让栈顶指针上移,再进行赋值操作
// 3. 第二个栈入栈,因为第二个栈的栈顶为MAXSIZE,所以让栈顶指针下移,再进行赋值操作
int push_stack(DqStack* s, int x, int k) {
//s->top[0] == MAXSIZE - 1
if(s->top[0] == s->top[1] - 1) {
printf("两个栈均满");
return 0;
}
if(k == 0) {
s->top[k]++;
} else {
s->top[k]--;
}
s->stack[s->top[k]] = x;
}
//双端栈出栈
//栈顶元素由参数返回
//1. 出栈前,先判断栈是否为空, 如果为空 return 0
// 2.取出栈顶元素值给x
// 3. 如果是第一个栈出栈,指针下移; 如果是第二个栈,则指针上移
// 4. 返回1表示出栈成功
int pop_stack(DqStack* s, int* x, int k) {
if((s->top[0] == -1 && k == 0)|| (s->top[1] == MAXSIZE && k == 1)) {
printf("栈为空");
return 0;
}
*x = s->stack[s->top[k]];
if(k == 0)
s->top[k]--;
else
s->top[k]++;
return 1;
}
栈与递归
什么是递归?
递归是指在一个函数、过程或数据结构定义的内部,直接或间接出现定义本身的应用。 递归的内部实现是通过一个工作栈来保存调用过程中的参数、局部变量和返回地址。
递归函数
一种直接或间接地调用自身函数的过程。
阶乘函数
int fact(int n) {
if(n == 0)
return 1;
else
return (n * fact(n - 1));
}
当一个函数在运行期间调用另一个函数时,在运行被调用函数之前,系统需要完成以下3件事
- 将所有的实参,返回地址等信息传递给被调用函数保存。
- 为被调用函数的局部变量分配存储区。
- 将控制转移到被调函数的入口。
而从被调用函数返回函数之前,系统也完成三件事
- 保存被调用函数的计算结果。
- 释放被调用函数的数据区。
- 根据被调用函数保存的返回地址,将控制转移到调用函数。
以下三种情况用到递归
- 递归定义的数学函数
- 具有递归特性的数据结构
- 可递归求解的问题
递归问题-> 用分治法求解
当多个函数嵌套调用时
遵循最后调用的函数是最先返回的
递归的优缺点
栈的应用
子程序的嵌套调用、操作系统的中断处理,在程序的编译和运行过程中,还要利用栈堆程序的语法进行检查,如括号的匹配、表达式的求值和函数的递归调用等。 当应用程序中需要使用与数据存储顺序相反的数据时,就要想到使用栈。
数制转换问题
将十进制数N转换为d进制数,利用辗转相除法。 N = (N div d) * d + N mod d (div整除运算,mod求与余运算)
- 定义一个栈变量s
- 定义一个整型变量x, 用来接收弹栈的元素
- 初始化栈
- 循环 条件(N)->一直循环 操作:入栈(s, N % d), 往栈s里入余数; N = N / d;(N一直整除d)
- 循环 条件(栈不为空) 操作:弹栈(s, &x)
- 输出x
void conversion(int N, int d) {
seqStack* s;
int x;
init_seqStack(&s);
while(N) {
push_seqStack(&s, N % d);
N = N / d;
}
while(!empty(s)) {
pop_seqStack(s, &x);
printf("%d ", x);
}
}
利用数组实现数制转换
- 定义一个栈数组,一个指针变量top(栈顶)
- 定义一个变量x,用来接收弹栈的余数
- top默认等于-1,表示空栈
- 循环 条件(N) 操作: 栈顶指针上移; 将余数入栈;N整除d
- 循环 条件(栈不为空) 操作:将栈顶的元素(余数)赋值给x; 栈顶指针下移; 输出x
void conversion(int N, int d) {
int stack[MAX], top;
int x;
top = -1;
//入栈与我们需要的操作的数据是截然相反的结果
while(N) {
top++;
stack[top] = N % d; //余数入栈
N = N / d;
}
//只要栈顶指针不指向-1,即栈不为空
while(top != -1) {
x = stack[top]; //余数按顺序出栈
top--;
printf("%d ", x);
}
}
利用链表实现数制转换
void conversion(int N, int d) {
linkStack* top; //栈顶指针
linkStack* p;
top = NULL; //栈顶指针默认为NULL
while(N) {
p = new linkStack; //申请新的栈顶结点空间
p->data = N % d;
p->next = top;
top = p;
N = N / d;
}
while(top != NULL) {
p = top;
printf("%d", p->data);
top = top->next;
delete p;
}
}
括号匹配检验
思路:
#include <iostream>
#include <string.h>
char pairs(char a) {
if(a == ')') return '(';
if(a == ']') return '[';
if(a == '}') return '{';
return 0;
}
bool isValid(char* s) {
int n = strlen(s);
if(n % 2 == 1) {
return false;
}
int stack[n + 1], top = 0;
//遍历字符串s
for(int i = 0; i < n; i++) {
char ch = pairs(s[i]);
if(ch) {
if(top == 0 || stack[top - 1] != ch) {
return false;
}
top--;
} else {
stack[top++] = s[i];
}
}
return top == 0;
}
int main()
{
char s[] = "(()()())";
printf("%d", isValid(s));
return 0;
}
队列
队列具有先进先出的特点,限定元素的操作分别在表的两端进行,通常表尾进行插入操作,表头进行删除操作。
队列的主要操作是入队和出队。顺序表示的循环队列的入队和出队操作,要注意判断队满或队空。凡是涉及队头或队尾指针的修改,都要将其对MAXSIZE求模。
链队列
#include <iostream>
typedef int ElemType;
typedef struct Node {
ElemType data;
struct Node* next;
}QNode, *QueueP;
typedef struct {
QueueP front, rear;
}LinkQueue;
LinkQueue* initQueue() {
LinkQueue* Q; //申请头尾指针节点
Q = new LinkQueue;
Q->front = Q->rear = new QNode;
if(Q->front != NULL) {
Q->front->next = NULL;
return Q;
} else {
return 0;
}
}
//链队列入队
void enqueue(LinkQueue* Q, ElemType x) {
QNode* p;
p = new QNode; //申请新节点
p->data = x;
p->next = NULL;
Q->rear->next = p; //将新节点插入队尾
Q->rear = p; //队尾指针指向新的节点
}
//判队空
//成功返回1, 失败返回0
int empty(LinkQueue* Q) {
if(Q->front == Q->rear) //队头队尾指向同一节点,队列为空
return 0;
else
return 1;
}
//链队列出队
int dequeue(LinkQueue* Q, ElemType* x) {
QNode* p;
//出队操作首先判断队列Q是否为空
if(empty(Q)) {
printf("队列为空\n");
return 0;
//否则,取出Q的队头元素,用x返回其值,修改头指针
} else {
p = Q->front->next;
Q->front->next = p->next;
*x = p->data;
delete p;
//当队列中最后一个元素被删除后,将队尾指针重复赋值为头结点
if(Q->front->next == NULL)
Q->rear = Q->front;
return 1;
}
}
循环队列
在队列的顺序存储结构中,除了用一组地址连续的存储单元依次存放从队列到队尾的元素外,也需设2个整形变量front和rear,分别指示队列队头元素和队尾元素的位置。
在队列的顺序存储结构中,初始化空队列时,令front=rear=0,每当插入新的队尾元素时,尾指针rear增1,;每当删除队头元素时,头指针front增1.所以,在非空队列中,头指针始终指向队列头元素,而尾指针始终指向队尾元素的下一个位置。
随着入队、出队的进行,会使整个队列整体向后移动,当队尾指针移到了最后,再有元素进入,就会出现溢出。此时,队列中并未真的占满,这种现象称为“假溢出”。 这是由于“队尾入,队首出”这种受限制的操作所造成的,真正队满的条件应是 rear - front == MAXSIZE
如何解决假溢出问题。通过循环队列这种方式。随着入队、出队的进行,此时,“队满”和“队空”的条件是相同的。
解决办法1:增设一个存储队列中元素个数的变量(count),当count=0时,队空。 当count = MAXSIZE时,队满
解决办法2:少用一个元素空间,当队列空间大小为MAXSZIE时,有MAXSIZE的元素就认为队满,队尾指针永远追不上头指针。
判断队满的条件:(rear + 1) % MAXSIZE = front
判队空的条件:front == rear
由循环队列的存储特性可知,如果应用程序中使用循环队列,则必须申请一个合理的队列长度;若无法预估所用队列的大小,则宜采用链队列。
#include <iostream>
#define MAXSIZE 100 //顺序队列可达到的最大长度
typedef int ElemType;
//循环队列
typedef struct {
ElemType data[MAXSIZE];
int front;
int rear;
} seQueue;
//循环队列的初始化
seQueue* initQueue() {
seQueue* Q;
Q = new seQueue;
Q->front = Q->rear = 0;
return Q;
}
//入队
// 1.先判断队列是否满,若满则出错
// 2.否则将新元素插入队尾,队尾指针加1
int enqueue(seQueue* Q, ElemType x) {
if((Q->rear + 1) % MAXSIZE == Q->front) {
printf("队列已满");
return 0;
} else {
Q->data[Q->rear] = x;
Q->rear = (Q->rear + 1) % MAXSIZE; //队尾指针增1
return 1;
}
}
//出队
//1.先判断队列是否为空,若空则报错
// 2.否则队头元素出队,队头指针增1
int dequeue(seQueue* Q, ElemType* x) {
if(Q->front == Q->rear) {
printf("队空");
return 0;
} else {
*x = Q->data[Q->front]; //将队头元素存储在变量x上
Q->front = (Q->front + 1) % MAXSIZE;
return 1;
}
}
队列的应用
许多应用程序需要处理排队等候问题。如售票系统、机场管理系统等。凡是符合先进先出原则的数学模型,都可以用队列来实现。
串
串是由0个或任意多个字符组成的有限序列, 串是一种其数据元素固定为字符的线性表,串上的操作主要是针对串的整体或串的一部分子串进行的。
串的相关术语
- 空串: 不含任何字符的串, 即串的长度 n = 0, 记为"".
- 空格串:由一个或多个称为空格的特殊字符组成的串,其长度是串中空格字符的个数。
- 主串:包含子串的串
- 模式匹配:子串的定位运算又称为串的模式匹配, 求子串在主串中第一次出现的第一个字符的位置
- 两个串相等: 两个串的长度相等且各个位置上对应的字符也都相同。
串的应用
1.在汇编和高级语言的编译程序中,源程序和目标程序都以字符串来表示。 2.在事务处理程序中,如客户的姓名、地址、邮政编码和货物名称等都是用字符串数据处理的。 3. 信息检索系统、文字编辑系统和语言翻译系统等。
串的基本操作
-
strAsign(S, chars) //串的赋值 初始条件:chars是字符串常量 操作结果:生成一个值等于chars的串S
-
strCopy(S, T) //串的复制
-
strLength(S) //求串的长度 1). 初始化一个变量i为0,用于统计串的长度 2). 循环:条件(串的字符数组元素不等于'\0') 操作:i++; 循环结束后,s的长度等于i 3). 返回串的长度
-
strCat(S, T) //串的连接
-
subString(sub, S, pos, len) //求子串 初始条件:串S存在 , 1 <= pos <= strLength(S) 且 1 <= len <= strLength(S) - pos+1
-
strIndex(S, T) //串的定位,模式匹配
-
strInsert(S, pos, T) //串的插入
-
strDelete(S, pos, len) //串的删除
-
strReplace(S, T, V) //串的替换
-
strEmpty(S) //判断栈空
-
strCompare(S, T) //串的比较
-
strClear(S) //串的清空
-
printStr(S) //输出串
-
createStr(S) //建立串
- 调用求串长度子函数
- 将strLength(S)函数值赋值给S->Len
串的存储结构
顺序存储
- 顺序串可以用一个字符型数组和一个整型变量来表示,其中,字符数组存储串,整型变量表示串的长度。 例如 串 S = "Beijing", 字符串从S.ch[0]单元开始存放,用'\0'来表示串的结束
- 存储方式 当计算机按字节为单位编址时,一个存储单元刚好存放一个字符,串中相邻的字符按顺序存储在地址相邻的存储单元中。 当计算机按字(1个字为32位)为单元编址时,一个存储单元由4个字节组成。
1)非紧凑存储:计算机字长为32位(4个字节), 用非紧凑格式,一个地址只能存放一个字符, 其优点是运算处理简单,但缺点是存储空间十分浪费。 2) 紧凑存储:一个地址能存4个字符, 优点是空间利用率高,缺点是对串中字符的处理效率较低。
#include <iostream>
#define MAXSIZE 100 //顺序串存储空间的总分配量
typedef struct {
char ch[MAXSIZE]; //存储串的字符数组
int Len; //存储串的长度
}String; //串结构定义
//求串的长度
int strLen(String* s) {
//i用来统计字符串的长度
int i = 0;
while(s->ch[i] != '\0')
i++;
s->Len = i;
return s->Len;
}
//建立串的算法
void createStr(String* s) {
//建立一个新串函数
fgets(s->ch, strLen(s), stdin);
s->Len = strLen(s);
}
//求子串
//求串S从第pos位置开始,长度为len的子串,并将其存入到串sub中
//操作成功,返回1;操作失败,返回0
int subString(String* s, String* sub, int pos, int len) {
int j;
//判断边界条件
//如果 pos小于1或pos大于字符串的长度或 len(子串的长度)小于1
if(pos < 1 || pos > s->Len || len < 1 || len > s->Len - pos + 1) {
sub->Len = 0;
printf("参数错误\n");
return 0;
} else {
for(j = 0; j < len; j++) {
sub->ch[j] = s->ch[pos + 1];
sub->ch[j] = '\0';
sub->Len = len;
return 1;
}
}
}
//删除子串
//将删除串s从指定位置i开始的连续每个字符
int strDelete(String* s, int i, int l) {
int k;
//判断删除位置和删除串长度是否出错
if(i + l - 1 > s->Len) {
printf("所要删除的子串越界");
return 0;
} else {
for(k = i + l -1; k < s->Len; k++, i++) {
s->ch[i - 1] = s->ch[k];
s->Len = s->Len - l;
s->ch[s->Len] = '\0';
return 1;
}
}
}
//插入子串的算法
int strInsert(String* s, String* s1, int i) {
int k;
if(i > s->Len + 1) {
printf("插入位置出错");
return 0;
} else if(s->Len + s1->Len > MAXSIZE) {
printf("两串长度超过存储空间长度!");
return 0;
} else {
for(k = s->Len - 1; k >= i; k--)
s->ch[s1->Len + k] = s->ch[k];
for(k = 0; k < s1->Len; k++)
s->ch[i + k - 1] = s1->ch[k];
s->ch[i + k - 1] = s1->ch[k];
s->Len = s->Len + s1->Len;
s->ch[s->Len] = '\0';
return 1;
}
}
//串定位
int strIndex(String* s, String* t) {
int i = 0, j = 0, k;
while(i < s->Len && j < t->Len) {
if(s->ch[i] == t->ch[i]) {
i++;
j++;
} else {
i = i - j + 1;
j = 0;
}
}
if(j >= t->Len)
k = i - t->Len + 1;
else
k = 1;
return k;
}
//串相等
int strCompare(String* s1, String* s2) {
int i = 0, flag = 0;
//当两个串没到字符串尾部时
while(s1->ch[i] != '\0' && s2->ch[i] != '\0') {
if(s1->ch[i] != s2->ch[i]) {
flag = 1;
break;
} else
i++;
}
if(flag == 0 && s1->Len == s2->Len)
return 0;
else
return s1->ch[i] - s2->ch[i];
}
//串连接
int strCat(String* s, String* t) {
int i, flag;
if(s->Len + t->Len <= MAXSIZE) {
s->ch[i] = t->ch[i - s->Len];
s->ch[i] = '\0';
s->Len += t->Len;
flag = 1;
} else if(s->Len < MAXSIZE) {
for(i = s->Len; i < MAXSIZE; i++)
s->ch[i] = t->ch[i - s->Len];
s->Len = MAXSIZE;
flag = 0;
} else
flag = 0;
return flag;
}
//子串替换算法
void strReplace(String* s, String* t, String* v) {
int i, m, n, p, q;
n = s->Len;
m = t->Len;
q = v->Len;
p = 1;
do {
i = strIndex(s, t);
if(i != -1) {
strDelete(s, i, m);
strInsert(s, v, i);
p = i + q;
s->Len = s->Len + q - m;
n = s->Len;
}
}while((p <= n - m + 1) && (i != -1));
}
int main()
{
return 0;
}
链式存储
用链表存储字符串,每个结点有2个域:一个数据域(data)和一个指针域(next) data——用于存放字符串中的字符
next——用于存放后继结点的地址
优点:插入、删除运算方便
缺点:存储、检索效率低
在各种字符串的处理系统中,所处理的字符串往往又长又多。这时就必须考虑字符串的存储密度。
存储密度 = 字符串所占的存储位 / 实际分配的存储位
字符串链式存储的存储密度小,存储空间比较浪费,因此不太实用。
堆存储
在实际应用中,往往要定义很多字符串,并且各个字符串长度在定义之前又无法确定,在这种情况下,可以采用堆存储(索引存储),这是一种动态存储结构。
堆存储的方法
- 开辟一块地址连续的存储空间,用于存储各字符串的值,该存储空间称为“堆”(自由存储区)。
- 另外建一个索引表,用来存储字符串的名字(name)、长度(length)和该串在“堆”中的起始地址。
- 程序执行过程中,每产生一个串,系统就会从“堆”中分配一块大小与串的长度相同的连续空间,用于存储该串的值,并且在索引表中增加一个索引项,用于登记该串的名字、长度和串的起始地址。
typedef struct {
char name[MAXLEN]; //串名
int length; //串长
char* start; //起始地址
}LNode;
串的模式匹配
子串的定位操作通常称作串的模式匹配(其中T被称为模式串),确定主串中所含子串(模式串)第一次出现的位置(定位)。是各种处理系统中最重要的操作之一。
例如:在一篇文章中查找某个关键字,把文章看作主串,关键字看作子串。
算法应用
- 搜索引擎
- 拼写检查
- 语言翻译
- 数据压缩
算法种类
- BF算法
- KMP算法(特别快)
数组和广义表
数组的定义
数组是由n个类型相同的数据元素组成的有限序列。其中,这n个数据元素占用一块地址连续的内存空间。数组中的数据元素,可以是原子类型(int、float、char等)的,也可以是一个线性表,这种类型的数组称为二维数组。
所以,一个n维数组可以看成是若干个n-1维数组组成的线性表。
二维数组可以看做是一个定长线性表,它的每一个元素也是一个定长线性表。可以用一个矩阵来表示一个二维数组。先行再列
计算数组的地址
数组的基本操作
由于数组一经生成,其数据元素的个数就固定了,元素在内存中的存储空间也固定了,因此不能往数组中插入新元素,也不能删除数组中的元素。
init_Array(A, n, d[]) //生成一个n维的数组A,其中d[]中存放各维数的下标值
Destroy_Array(A) /销毁一个n维数组A并释放器占用的内存空间
getValue(A, idx[], val) //从A中读取某个元素的值,存入变量val中,并将钙元素的各维下标值入idx[]
setValue(idx[], v, A) //将变量v的值赋值给n维数组A中某个元素,该元素的位置由idx[]下标值来确定。
树和二叉树
树的定义
树是n(n >= 0)个结点的有限集合。 当n = 0时,则这棵树为空树。 在一颗非空树中:
- 有且仅有一个根节点, 根结点无直接前驱,但有0个或多个直接后继
- 除根节点外的其余结点可被分成m(m >= 0)个互不相交的有限集合,每个集合本身又是一棵树,称为根节点的子树。每个子树的根节点有且仅有一个直接前驱,但有0个或多个直接后继。
树的深度(高度)为4
树可以描述为二元组的形式 T = (D, R) D为树中结点的集合,R为树中结点之间关系的集合 D = {a, b, c, d, e, f} R = {(a, b), (a, c), (a, d), (c, e), (c, f)}
遍历二叉树
是指从根结点出发,按照某种次序依次访问二叉树中所有的结点,使得每个结点被访问依次且仅被访问一次。 四种遍历方式分别为:先序遍历、中序遍历、后序遍历、层序遍历。
先序遍历 (根 左 右)
- 若二叉树为空,则为空操作
- 访问根节点
- 先序遍历节点的左子树
- 先序遍历节点的右子树
#include <iostream>
typedef int ElemType;
typedef struct BitNode {
ElemType data; //数据域
struct BitNode *lChild, *rChild; //左右孩子指针
}BitNode, *BitTree;
void preOrder(BitTree bt) {
if(bt != NULL) {
printf("%d", bt->data); //根
preOrder(bt->lChild); //左
preOrder(bt->rChild); //右
}
}
非递归算法
//根左右
void preOrder(Node* root) {
stack<Node*> s;
Node* cur = root;
while(true) {
while(cur != NULL) {
cout << cur->data << " ";
s.push(cur);
cur = cur->left;
}
if(s.empty())
return;
cur = s.top();
s.pop();
cur = cur->right;
}
}
中序遍历 (左 根 右)
- 若二叉树为空,则为空操作
- 中序遍历根节点的左子树
- 访问根节点
- 中序遍历根节点的右子树
void inOrder(BitTree bt) {
if(bt != NULL) {
inOrder(bt->lChild);
printf("%d", bt->data);
inOrder(bt->rChild);
}
}
后序遍历 (左 右 根)
- 若二叉树为空,则为空操作
- 后序遍历根节点的左子树
- 后序遍历根节点的右子树
- 访问根节点
递归写法
void postOrder(BitTree bt) {
if(bt != NULL) {
postOrder(bt->lChild);
postOrder(bt->rChild);
printf("%d", bt->data);
}
}
非递归写法
#include<bits/stdc++.h>
using namespace std;
typedef struct Node {
int data; //数据域
struct Node* left; //指向左子树的指针
struct Node* right; //指向右子树的指针
//初始化树的结点
Node(int data) {
this->data = data;
left = right = NULL;
}
}Node;
/*
1 stack->1
/ \ stack->2->1
2 3 stack->4->2->1
/ \
4 5
*/
//中序遍历
void inOrder(Node* root) {
//通过栈的方式实现树的遍历
stack<Node*> s;
Node* cur = root; //让指针cur指向根节点
//只要指针cur不指向空 或 栈不为空
while(cur != NULL || s.empty() == false) {
//只要cur不为空,就将根节点开始入栈,然后遍历左子树
// 并将其值依次入栈
while(cur != NULL) {
s.push(cur);
cur = cur->left;
}
//cur为空时,就让它指向栈顶
cur = s.top();
//依次从栈顶弹出值
s.pop();
cout << cur->data << " ";
cur = cur->right;
}
}
int main()
{
Node* root = new Node(1);
root->left = new Node(2);
root->right = new Node(3);
root->left->left = new Node(4);
root->left->right = new Node(5);
inOrder(root);
return 0;
}
层序遍历
层序遍历嘛,就是按层,从上到下,从左到右遍历,这个没啥好说的。
搜索/查询
顺序查找
#include <iostream>
using namespace std;
int linearSearch(int arr[], int length, int value) {
//遍历数组, 逐个比较,如果数组中的值等于value,返回要查找值的下标,否则返回-1
for(int i = 0; i < length; i++) {
if(arr[i] == value)
return i;
}
return -1;
}
int main()
{
int arr[] = {1, 4, 0, 2, 3, 8, 10, 22, 90, 6};
int length = sizeof(arr) / sizeof(arr[0]);
int value = 10;
int result = linearSearch(arr, length, value);
(result == -1) ? cout << "查找失败" : cout << "查找成功, 该元素的下标为 " << result;
return 0;
}
二分查找
#include <iostream>
using namespace std;
//二分查找------只能对有序的序列进行查找
int binarySearch(int arr[], int length, int value) {
int low = 0;
int high = length - 1;
int mid;
while(low <= high) {
mid = (low + high) / 2;
if(value == arr[mid]) {
return mid;
} else if(value < arr[mid]) {
high = mid - 1;
} else
low = mid + 1;
}
return -1;
}
int main()
{
int arr[] = {2, 4, 6, 8, 10, 12, 14, 16};
int length = sizeof(arr) / sizeof(arr[0]);
int value = 10;
int result = binarySearch(arr, length, value);
(result == -1) ? cout << "查找失败" : cout << "查找成功, 该元素的下标为 " << result;
return 0;
}
搜索二叉树
#include <iostream>
using namespace std;
/**
* 若左子树和右子树不为空,则搜索二叉树所有左子树上的结点值都小于根节点的值,所有右子树上的结点值都大于根节点的值
* 搜索左子树和右子树同样满足二叉搜索树的性质。
*/
//搜索二叉树
typedef struct Node {
int data;
struct Node* lchild;
struct Node* rchild;
Node(): data(0), lchild(nullptr), rchild(nullptr) {}
Node(int value): data(value), lchild(nullptr), rchild(nullptr) {}
} Node, *BSTree;
//从二叉搜索树中查询某个值
BSTree search(BSTree root, int data) {
//如果根节点为空或 要查找的值等于根节点的值 就返回根节点
if(root == nullptr || root->data == data)
return root;
//如果要查找的值大于根节点的值,就从右子树上找
if(root->data < data)
return search(root->rchild, data);
//如果要查找的值小于根节点的值,就从左子树上找
else
return search(root->lchild, data);
}
//插入
BSTree insert(BSTree &root, int data) {
//动态创建一个新的节点
BSTree newNode = new Node;
//如果根节点为空,就插入一个新的节点
if(!root) {
newNode->data = data;
newNode->lchild = nullptr;
newNode->rchild = nullptr;
return newNode;
}
//如果要插入的值大于根节点的值,就从右子树上插入
if(root->data < data)
root->rchild = insert(root->rchild, data);
//如果要插入的值小于根节点的值,就从左子树上插入
else
root->lchild = insert(root->lchild, data);
return root;
}
//中序遍历二叉树,得到的是一个有序的序列
void inorder(BSTree root) {
//如果根节点为空,就退出
if(!root)
return;
//中序:左根右
inorder(root->lchild);
cout << root->data << " ";
inorder(root->rchild);
}
//找最小值
BSTree minValueNode(BSTree root) {
BSTree cur = root; //cur指向根结点
//一直往左遍历, 直到cur和cur的左子树为空
while(cur && cur->lchild)
cur = cur->lchild;
return cur;
}
//删除节点
/**
* 1. 叶子结点直接删
* 2. 单子结点(左子或右子),子结点顶替被删除的结点
* 3. 双子结点:从右子树种找到最小值(min)的结点或从左子树中找到最大值(max)的结点,顶替被删除的结点
*/
BSTree deleteNode(BSTree root, int data) {
//判空
if(root == nullptr)
return root;
//如果要删除的结点值大于根节点的值,就从右子树开始找
if(data > root->data) {
root->rchild = deleteNode(root->rchild, data);
//如果要删除的结点值小于根节点的值,就从左子树开始找
} else if(data < root->data) {
root->lchild = deleteNode(root->lchild, data);
} else {
//如果要删除的结点值等于根结点的值,就删除
//叶子结点, 直接删
if(root->lchild == nullptr && root->rchild == nullptr)
return nullptr;
// free(root);
//双子结点
else if(root->rchild && root->lchild) {
BSTree temp = minValueNode(root->rchild); //从右子树中找出最小值的结点
root->data = temp->data;
root->rchild = deleteNode(root->rchild, temp->data);
}
else {
//单子结点
if(root->lchild == nullptr) {
BSTree temp = root->rchild;
root->data = temp->data;
delete root;
return temp;
} else {
BSTree temp = root->lchild;
root->data = temp->data;
delete root;
return temp;
}
}
}
return root;
}
int main()
{
BSTree root = nullptr;
// Node no;
root = insert(root, 50);
root = insert(root, 30);
root = insert(root, 70);
root = insert(root, 20);
root = insert(root, 40);
root = insert(root, 60);
root = insert(root, 80);
inorder(root);
cout << "\n---------------" << endl;
deleteNode(root, 20);
// inorder(root);
//删除30
deleteNode(root, 30);
inorder(root);
return 0;
}
散列表
排序
插入排序
将数组分成一个排序好和未排序好的元素序列,从未分类元素序列中挑选值并放置到排序序列的正确位置。
思路:
- 按升序对大小为n的数组进行排序, 即从arr[1]到arr[n]在数组中重新排列
- 将当前元素(i),记录到临时变量tmp中,与其前身(j)进行比较
- 如果关键元素比前身元素小,则将其与之前的元素进行比较。将较大的元素向上移动一个位置,为交换的元素腾出空间。
直接插入排序
void insertSort(int arr[], int len) {
//检查数据合法性
if(arr == nullptr || len <= 0)
return;
for(int i = 1; i < len; i++) {
int tmp = arr[i];
int j;
for(j = i - 1; j >= 0; j--) {
//如果比tmp大,就把值往后移动一位
if(arr[j] > tmp) {
arr[j + 1] = arr[j];
} else {
break;
}
}
//插入到正确位置
arr[j + 1] = tmp;
}
}
void InsertSort(int arr[], int len) {
//检查数据合法性
if(arr == nullptr || len <= 0)
return;
int key, i, j;
for(i = 1; i < len; i++) {
key = arr[i]; //记录当前元素
//j用来记录前身元素
j = i - 1;
// j >= 0 表示不能越界 当前元素比前身元素小,就让前身元素往后移,腾位置
while(j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
//当循环结束后,表示arr[j] < key, 即前身元素小于当前元素,那我们就将当前元素插入到前身元素后边
arr[j + 1] = key;
}
}
折半插入排序
可以用来减少直接插入排序中的比较次数,该排序使用二分查找在每个循环中插入所选值的正确位置。
void BinaryInsertSort(int arr[], int len) {
//检查数据合法性
if(arr == nullptr || len < 0)
return;
int key, low, high;
for(int i = 1; i < len; i++) {
key = arr[i];
low = 0;
high = i - 1; //low~high是已排好序的元素序列
//二分查找找出第一个比key大的数
while(low <= high) {
int mid = (low + high) / 2;
if(arr[mid] > key)
high = mid - 1;
else
low = mid + 1;
} //循环结束,high+1则为插入位置
//将元素依次后移,直到high+1这个位置也移动到后边去
for(int j = i - 1; j >= low; j--) {
arr[j + 1] = arr[j];
}
// arr[low] = key;
arr[high + 1] = key;
}
}
希尔排序
void shellSort2(int arr[], int n) {
for(int gap = n / 2; gap > 0; gap /= 2) {
for(int i = gap; i < n; i++) {
int temp = arr[i];
int j = i;
while(j >= gap && arr[j - gap] > temp) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = temp;
}
}
}
快速排序
#include <iostream>
using namespace std;
int partition(int arr[], int low, int high) {
int pivot = arr[low]; //将第一个数作为中心点
while(low < high && arr[high] >= pivot) {
high--;
}
arr[low] = arr[high];
while(low < high && arr[low] <= pivot) {
low++;
}
arr[high] = arr[low];
arr[low] = pivot;
return low;
}
void quickSort(int arr[], int low, int high) {
if(low < high) {
int part = partition(arr, low, high);
//左半部分
quickSort(arr, low, part - 1);
//右半部分
quickSort(arr, part + 1, high);
}
}
void PrintArray(int arr[], int len) {
for(int i = 0; i < len; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
int main() {
int arr[] = {20, 1, 5, 6, 2, 4, 7};
int len = sizeof(arr) / sizeof(arr[0]);
quickSort(arr, 0, len - 1);
PrintArray(arr, len);
return 0;
}
选择排序
时间复杂度 O(n^2)
特点:
- 由于算法过程中可能改变相同关键字的前后顺序,可能产生不稳定现象。
- 可用于链式存储结构
- 移动记录次数少,当每个记录占用的空间较多时,其排序效率较高
原理: 找到数组中的最小值并将其放在第一个位置。在那之后,它找到第二个最小的值并把它放到第二个位置。重复这个过程,直到整个数组排序完毕。
伪代码:
- for i = 1 to (n - 1)
- min = i //设当前元素为最小值
- for j = i + 1 to n
- if list[j] < list[min] then min = j; end if end for if indexMin != i then swap list[min] and list[i] end if end for
#include <iostream>
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
void selectionSort(int arr[], int n) {
int i, j, min;
for(i = 0; i < n - 1; i++) {
min = i; //假设当前元素是最小的元素
//从unsorted array找到最小值
for(j = i + 1; j < n; j++) {
if(arr[j] < arr[min]) {
min = j;
}
}
swap(&arr[min], &arr[i]);
}
}
int main()
{
int arr[5] = {3, 5, 1, 4, 2};
int n = sizeof(arr) / sizeof(arr[0]);
selectionSort(arr, n);
printf("\n排序后,数组为:");
for(int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
堆数据结构
堆的常用方法:
- 构建优先队列
- 支持堆排序
- 快速找出一个集合中的最小值(或者最大值)
堆数据结构是一个完整的二叉树,满足堆属性,其中任何给定的节点是 最大堆:根节点的值是所有节点中最大的 最小堆:根节点的值是所有节点中最小的
#include <iostream>
using namespace std;
int n = 0;
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
//堆的初始化
void heapify(int arr[], int n, int i) {
if(n == 1) {
cout << "堆里只有一个元素";
} else {
int largest = i;
int l = 2 * i + 1;
int r = 2 * i + 2;
if(l < n && arr[l] > arr[largest])
largest = l;
if(r < n && arr[r] > arr[largest])
largest = r;
if(largest != i) {
swap(&arr[i], &arr[largest]);
heapify(arr, n, largest);
}
}
}
void insert(int arr[], int val) {
if(n == 0) {
arr[0] = val;
n++;
} else {
arr[n] = val;
n++;
for(int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
}
}
void deleteNode(int arr[], int num) {
int i;
for(i = 0; i < n; i++) {
if(num == arr[i])
break;
}
swap(&arr[i], &arr[n - 1]);
n--;
for(int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
}
void printArray(int arr[], int n) {
for(int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
int main() {
int arr[8];
insert(arr, 3);
insert(arr, 4);
insert(arr, 9);
insert(arr, 5);
insert(arr, 2);
// int n = sizeof(arr) / sizeof(arr[0]);
cout << "Max-Heap Array:";
printArray(arr, n);
deleteNode(arr, 4);
cout << "\n删除4之后";
printArray(arr, n);
return 0;
}
堆排序
#include <iostream>
using namespace std;
//最大堆调整
void heapify(int arr[], int n, int i) {
//找到最大结点值
int largest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if(left < n && arr[left] > arr[largest])
largest = left;
if(right < n && arr[right] > arr[largest])
largest = right;
//如果根结点值不是最大的,就交换
if(largest != i) {
swap(arr[i], arr[largest]);
//第一次调整后,其他子树并没有满足堆的特性
//所以还要再递归一次, 直到largest == i
heapify(arr, n, largest);
}
}
//Build-Max-Heap 将自下而上的调用 Max-Heapify 来改造数组,建立最大堆
void heapSort(int arr[], int n) {
//建立最大堆
for(int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);
for(int i = n - 1; i >= 0; i--) {
swap(arr[0], arr[i]);
heapify(arr, i, 0);
}
}
void printArray(int arr[], int n) {
for (int i = 0; i < n; ++i)
cout << arr[i] << " ";
cout << "\n";
}
int main()
{
int arr[] = {1, 12, 9, 5, 6, 10};
int n = sizeof(arr) / sizeof(arr[0]);
heapSort(arr, n);
cout << "Sorted array is \n";
printArray(arr, n);
return 0;
}
归并排序
- 给一个未排序的数组,怎么实现排序呢
- 先拆后合, 先找到中间值,然后一分为二,拆成左边和右边分别递归调用mergeSort()
- 最后合并, 调用merge(), 把2段合并好的数合并成一个大的排序好的数
void merge(int arr[], int start, int mid, int end) {
int n1 = mid - start + 1;
int n2 = end - mid;
int L[n1], R[n2];
for(int i = 0; i < n1; i++) {
L[i] = arr[start + i]; //复制数组
}
for(int i = 0; i < n2; i++) {
R[i] = arr[mid + 1 + i];
}
int p1 = 0; //S1: start~mid S2: mid+1 ~ end
int p2 = 0;
int p = start;
//比较两副牌的大小, 从每幅牌的第一张开始比较, 小的放入到新数组中
while(p1 < n1 && p2 < n2) {
if(L[p1] <= R[p2]) {
arr[p] = L[p1]; //小的元素放入到新数组中
p1++;
// p++;
} else {
arr[p] = R[p2];
p2++;
// p++;
}
//每判断完一次,更新新数组的下标
p++;
}
//当左边还有牌时,就将剩余的牌添加到新数组中
while(p1 < n1) {
arr[p] = L[p1];
p1++;
p++;
}
//当右边还有牌时,就将剩余的牌添加到新数组中
while(p2 < n2) {
arr[p] = R[p2];
p2++;
p++;
}
}
//归并排序
void mergeSort(int arr[], int start, int end) {
if(start < end) {
int mid = start + (end - start) / 2;
//拆成左边和右边分别递归调用mergeSort
mergeSort(arr, start, mid);
mergeSort(arr, mid + 1, end);
//合并
merge(arr, start, mid, end);
}
}
/*
void merge(int arr[], int start, int mid, int end) {
int n = end - start + 1; //数组长度
int temp[n]; //临时数组,将2个排好序的数组合并成一个大的排好序的数组
// int n1 = mid - start + 1;
// int n2 = end - mid;
int L[100], R[100];
for(int i = 0; i < mid-start + 1; i++) {
L[i] = arr[start + i]; //复制数组
}
for(int i = 0; i < end - mid; i++) {
R[i] = arr[mid + 1 + i];
}
int p1 = 0; //S1: start~mid S2: mid+1 ~ end
int p2 = 0;
int p = start; //temp数组的下标
//比较两副牌的大小, 从每幅牌的第一张开始比较, 小的放入到新数组中
while(p1 < mid && p2 < end) {
if(L[p1] < R[p2]) {
temp[p] = L[p1]; //小的元素放入到新数组中
p1++;
// p++;
} else {
temp[p] = R[p2];
p2++;
// p++;
}
//每判断完一次,更新新数组的下标
p++;
}
//当左边还有牌时,就将剩余的牌添加到新数组中
while(p1 < mid) {
temp[p] = L[p1];
p1++;
p++;
}
//当右边还有牌时,就将剩余的牌添加到新数组中
while(p2 < end) {
temp[p] = R[p2];
p2++;
p++;
}
}
*/
桶排序、基数排序
平时c语言练习
switch
#include <stdio.h>
int main() {
int day;
printf("输入数字:");
scanf("%d", &day);
switch(day) {
case 1:
printf("Monday");
break;
case 2:
printf("Thusday");
break;
case 3:
printf("Wednesday");
break;
case 4:
printf("Thursday");
break;
case 5:
printf("Friday");
break;
case 6:
printf("Saturday");
break;
case 7:
printf("Sunday");
break;
default:
printf("滚");
break;
}
return 0;
}
#include <stdio.h>
int main() {
int score;
printf("输入成绩:");
scanf("%d", &score);
switch(score / 10) {
case 10:
case 9:
printf("A");
break;
case 8:
printf("B");
break;
case 7:
printf("C");
break;
case 6:
printf("D");
break;
default:
printf("E");
break;
}
/*
if(score >= 90 && score <= 100) {
printf("A");
} else if(score >= 80) {
printf("B");
} else if(score >= 70) {
printf("C");
} else if(score >= 60) {
printf("D");
} else {
printf("E");
}
*/
return 0;
}
#include <stdio.h>
int main() {
int x;
scanf("%d", &x);
x = (x % 2 == 1) ? "Odd" : "Even";
printf("%s", x);
return 0;
}
C++ STL
STL算法基本用法
#include <iostream>
#include <algorithm>
#include <vector> //向量
#include <numeric>
using namespace std;
void PrintArray(int arr[], int size) {
for (int i = 0; i < size; i++)
cout << arr[i] << " ";
cout << endl;
}
void PrintVector(vector<int> vect, int size) {
for (int i = 0; i < size; i++)
cout << vect[i] << " ";
cout << endl;
}
using namespace std;
int main()
{
int arr[] = {6, 5, 12, 10, 9, 1, 10};
int size = sizeof(arr) / sizeof(arr[0]);
//1.定义向量
vector<int> vect(arr, arr+size); //初始化有数组值的向量
cout << "Vector is:";
PrintVector(vect, size);
//2.给向量排序——升序
sort(vect.begin(), vect.end()); //sort函数(startAddr, endAddr)
cout << "Vector after sorting is:";
PrintVector(vect, size);
//3.将向量中的元素反转
reverse(vect.begin(), vect.end());
cout << "Vector after reversing is:";
PrintVector(vect, size);
//4.向量最大值和最小值
cout << "向量中的最大元素为:" << *max_element(vect.begin(), vect.end()) << endl;
cout << "向量中的最小元素为:" << *min_element(vect.begin(), vect.end()) << endl;
//5.向量元素累加
//accumulate(first_iterator, last_iterator, 初始值)
cout << "向量中的元素总和为:" << accumulate(vect.begin(), vect.end(), 0) << endl;
//6.计算元素个数
cout << "元素10出现的个数为:" << count(vect.begin(), vect.end(), 10) << endl;
//7.查找向量中的元素出现的个数,并指向向量的最后地址
find(vect.begin(), vect.end(), 6) != vect.end() ? cout << "找到了" : cout << "没找到";
/*
sort(arr, arr + size); //startAddr endAddr
if(binary_search(arr, arr+size, 10)) {
cout << "找到该元素";
} else {
cout << "未找到该元素";
}
*/
// PrintArray(arr, size);
return 0;
}
向量(vector)
向量是一个能够存放任意类型的动态数组
构造函数
- vector(); 创建一个空的vector
- vector(int n); 创建一个vector,元素个数为n
- vector(int n, const t& t); 创建一个vector,元素个数为n, 且值均为t
- vector(const vector&)复制构造函数
- vector(begin, end); 复制[begin,end)区间内另一个数组的元素到vector中
增加函数
- void push_back(const T& x) 向量尾部增加一个元素x
删除函数
- void pop_back()删除向量中最后一个元素
- void clear() 清空向量中所有元素
遍历函数
- iterator begin(): 返回向量头指针,指向第一个元素
- iterator end(): 返回向量尾指针,指向最后一个元素
判断函数
bool empty() const: 判断向量是否为空,若为空,则向量中无元素
大小函数
int size() const: 返回向量中元素的个数, 当前使用数据的大小 int capacity() const: 返回当前向量所能容纳的最大元素值,当前vector分配的大小
其他函数
- void swap(vector&) 交换2个同类型向量的数据
- void assign(int n, const T& x): 设置向量中前n个元素的值为x
基本用法
- Vector<类型> 标识符
- Vector<类型> 标识符(最大容量)
- Vector<类型> 标识符(最大容量,初始所有值)
- Vector<vector> v 二维向量
- 声明一个迭代器 vector::iterator it; //可以用来访问vector容器,遍历或指向容器的元素
vector<int>::iterator it;
for(it = vec.begin(); it != vec.end(); it++)
cout << *it << " ";
相当于哈希表(unordered_map)
记录元素的哈希值, 根据hash值判断元素是否相同,即unodered_map内部元素是无序的。
-
如何使用哈希表? 添加 #include<unordered_map> 和 using namespace std;
-
容器模板定义
template <class Key, //键值对中的键类型
class T, //键值对中的值类型
class Hash = hash<Key>, //容器内部存储值所对应的哈希函数
class Pred = equal_to<Key>, //判断各个键值对相同的规则
class Alloc = allocator<pair<const Key, T>> //指定分配器对象的类型
>class unordered_map;
必须显式给前2个参数传值。
- 常用参数
1 . <Key,T> 存储键值对的类型(键和值)
2 . Hash = hash 指定容器在存储各个键值对时要使用的哈希函数,默认哈希函数只适用于基本数据类型。
3. Pred = equal_to 判断各个键值对是否相等的规则
创建哈希表容器
-
通过调用unordered_map模板类的默认构造函数,//可以创建unordered_map容器
std::unordered_map<std::string, std::string> myMap {
{"百度", "www.baidu.com"},
{"谷歌", "www.google.com"}
};
常用的成员方法
- begin() 返回指向容器中第一个键值对的正向迭代器
- end() 返回指向容器中最后一个键值对之后位置的正向迭代器
- empty() 若容器为空,则返回true;否则false
- max_size() 返回容器所能容纳键值对的最大个数
- size() 返回当前容器中存有键值对的个数
- swap() 交换 2 个 unordered_map 容器存储的键值对,前提是必须保证这 2 个容器的类型完全相等。
- count(key) 在容器中查找以 key 键的键值对的个数。
- insert() 向容器中添加新键值对。
- erase() 删除指定键值对。
- clear()清空容器,即删除容器中存储的所有键值对。
- at(key) 返回容器中存储的键 key 对应的值,如果 key 不存在,则会抛出 out_of_range 异常。
- find(key) 查找以 key 为键的键值对,如果找到,则返回一个指向该键值对的正向迭代器;反之,则返回一个指向容器中最后一个键值对之后位置的迭代器(如果 end() 方法返回的迭代器)。
- hash_function() 返回当前容器使用的哈希函数对象。
auto,在编写代码时有时我们希望编译器能够帮我们自动推断变量的类型,使用关键字 auto
#include <iostream>
#include <string>
#include <unordered_map> //哈希表
using namespace std;
int main()
{
//创建空的umap容器
unordered_map<string, string> umap;
//std::unordered_set<std::string> umap2;
//umap2.emplace("www.4399.com");
//向umap容器添加新键值对
umap.emplace("百度", "www.baidu.com");
umap.emplace("谷歌", "www.google.com");
//输出umap存储键值对的数量
cout << "umap size = " << umap.size() << endl;
//使用迭代器输出umap容器存储的所有键值对
for(auto iter = umap.begin(); iter != umap.end(); ++iter) {
cout << iter->first << " " << iter->second << endl;
}
// cout << *
return 0;
}