ACWING算法笔记

这篇博客详细介绍了各种排序算法,包括快速排序、归并排序、冒泡排序、选择排序、直接插入排序和希尔排序,以及它们的时间复杂度分析。此外,还涉及到了高精度计算、双指针算法、位运算、离散化、链表操作和数据结构如堆、哈希表等。同时,博主探讨了C++ STL的使用技巧和各种图、树的遍历方法,以及最短路问题和最小生成树的相关算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

排序算法

  • 快排是不稳定的(可以加关键字,即二元组进行排序)

快速排序算法(分治思想)

步骤

  1. 确定分界点:可以用最小下标值、中间值以及随机选取
  2. 调整区间:双指针
  3. 递归处理左右两段

代码模板

几种模板详解转载快速排序
注意y总的do-while模板边界。如果选择i-1,则分界点不能选择q[l];若选择j,则分界点不能选择q[r],否则可能会死循环。.

void quick_sort(int q[], int l, int r)
{
    if (l >= r) return;

    int x = q[l], i = l, j = r; // 这里必须要用q[l]
    while (i <= j)
    {
        while (i <= j && q[i] <= x) i ++ ;
        while (i <= j && q[j] >= x) j -- ;

        if (i < j) swap(q[i], q[j]);
    }

    swap(q[l], q[j]); // 记得把pivot换到对应的位置
    // 一次划分完后区间被分为[l, j - 1][j][j + 1, r], j就是选的pivot
    quick_sort(q, l, j - 1);
    quick_sort(q, j + 1, r);
}

单链表的快速排序(只改变节点的val值)

//单链表的快速排序:
class Solution {
public:
        ListNode* sortList(ListNode* head) {
		quickSort(head, nullptr);
		return head;
	}

	void quickSort(ListNode* head, ListNode* tail) {
		if (head == tail || head->next == nullptr) {
			return;
		}

		ListNode* mid = partition(head, tail);

		quickSort(head, mid);
		quickSort(mid->next, tail);
	}

	ListNode* partition(ListNode* head, ListNode * tail) {

		int pivot = head->val;
		ListNode* s = head;
		ListNode* cur = head->next;
		while (cur != nullptr && cur != tail)
		{
			if (cur->val < pivot) {
				s = s->next;
				swap(s, cur);
			}
			cur = cur->next;
		}
		swap(s, head);
		return s;
	}


	void swap(ListNode* a, ListNode* b) {
		int temp = a->val;
		a->val = b->val;
		b->val = temp;
	}

};

单链表快速排序(改变链表的节点位置)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* get_tail(ListNode* head)
    {
        while(head->next) head=head->next;
        return head;
    }
    ListNode* quickSortList(ListNode* head) {
        if(!head||!head->next) return head;
        
        auto left=new ListNode(-1),mid=new ListNode(-1),right=new ListNode(-1);
        auto ltail=left,mtail=mid,rtail=right;
        int val=head->val;
        
        for(auto p=head;p;p=p->next)
        {
            if(p->val<val) ltail=ltail->next=p;
            else if(p->val==val) mtail=mtail->next=p;
            else rtail=rtail->next=p;
        }
        ltail->next=mtail->next=rtail->next=NULL;
        left->next=quickSortList(left->next);
        right->next=quickSortList(right->next);
        get_tail(left)->next=mid->next;
        get_tail(left)->next=right->next;
        return left->next;
    }
};

时间复杂度分析

  • 快排的平均时间复杂度是O(nlogn).

归并排序

  • 归并排序是稳定的

步骤

  1. 确定分界点:数组的中心点
  2. 递归排序left,right
  3. 归并:左边右边合二为一(双指针算法)

代码模板

#include <iostream>

using namespace std;

const int N = 1e6 + 10;

int a[N], tmp[N];

void merge_sort(int q[], int l, int r)
{
    if (l >= r) return;

    int mid = l + r >> 1;

    merge_sort(q, l, mid), merge_sort(q, mid + 1, r);

    int k = 0, i = l, j = mid + 1;
    while (i <= mid && j <= r)
        if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
        else tmp[k ++ ] = q[j ++ ];
    while (i <= mid) tmp[k ++ ] = q[i ++ ];
    while (j <= r) tmp[k ++ ] = q[j ++ ];

    for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);

    merge_sort(a, 0, n - 1);

    for (int i = 0; i < n; i ++ ) printf("%d ", a[i]);

    return 0;
}

归并的迭代方法

void mergesort(int k[],int n)
{
	int i, left_min,left_max,right_min,right_max;
	int *temp=(int *)malloc(n*sizeof(int));
	for(i=1;i<n;i*=2)
	{
		int next=0;
		for(left_min=0;left_min<n-i;left_min=right_max)
		{
			right_min=left_max=left_min+i;
			right_max=left_max+i;
			if(right_max>n)
			{
				right_max=n;
			}
			while(left_min<left_max&&right_min<right_max)
			{
				if(k[left_min]<k[right_min])
				{
					temp[next++]=k[left_min++];
				}
				else
				{
					temp[next++]=k[right_min++];
				}
			}
			while(left_min<left_max) temp[next++]=k[left_min++];
			while(right_min<right_max) temp[next++]=k[right_min++];
		}
		for(int m=0;m<n;m++) k[m]=temp[m];
		
	}
}

归并实现求数列逆序对的个数

#include<iostream>

using namespace std;
int n;
const int N=100010;
int a[N];
long long res;
void mergesort(int k[],int i,int j)
{
    if(i>=j) return;
    int mid=i+j>>1;
    mergesort(k,i,mid);
    mergesort(k,mid+1,j);
    int temp[N],l=i,r=mid+1,u=0;
    while(l<=mid&&r<=j)
    {
        if(k[l]<=k[r]) temp[u++]=k[l++];
        else {
            temp[u++]=k[r++];
            res+=mid-l+1;
        }
    }
    while(l<=mid) temp[u++]=k[l++];
    while(r<=j) temp[u++]=k[r++];
    u=0;
    for(int q=i;q<=j;q++) k[q]=temp[u++];
}
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++) scanf("%d",&a[i]);
    mergesort(a,0,n-1);
    cout<<res<<endl;
    //for(int q=0;q<n;q++) cout<<a[q]<<endl;
    return 0;
}

时间复杂度分析

  • 归并排序的时间复杂度一直是O(nlogn)

冒泡排序

基本思想

  • 两两相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。

代码实现

//可以设置一个flag变量来进行剪枝操作;
void BubbleSort(int k[],int n)
{
	int i,j,temp;
	for(i=0;i<n-1;i++)
		for(j=n-1;j>i;j--)
		{
			if(k[j-1]>k[j])
			{
				temp=k[j-1];
				k[j-1]=k[j];
				k[j]=temp;
			}
		}
}

时间复杂度分析

  • 冒泡时间复杂度是O(n^2)

选择排序

基本思想

  • 通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i个记录交换。

代码实现

void SelectSort(int k[],int n)
{
	int i,j,temp;
	for(i=0;i<n-1;i++)
	{
		int min=i;
		for(j=i+1;j<n;j++)
		{
			if(k[j]<k[min])
			{
				min=j;
			}
		}
		if(min!=i)
		{
			temp=k[i];
			k[i]=k[min];
			k[min]=temp;
		}
	}
}

单链表选择排序代码实现

#include <stdio.h>
#include <stdlib.h>
struct node{
    int data;
    struct node *next;
}; 
void printList(struct node *head){
    struct node *t=head;
    while(t){
        printf("%d ",t->data);
        t=t->next;
    }
}
struct node * selectSort(struct node *head)
/*选择排序 
在原链表中一轮轮地找最大的那个结点,找到之后就把它从原链表中抽出来用头插法加到新的链表中。 
需要注意这个最大的结点是否为头结点 
*/
{
    struct node *head1,*max,*premax,*t,*pret;
    //head1:新链表的头指针;max:原链表中最大的结点;premax:最大结点的前驱结点;t:用于遍历原链表寻找最大结点;pret:t的前驱结点
    head1=NULL;
    while(head)//一遍遍地找原链表中当前最大结点 
    {
        max=head;
        premax=NULL;
        pret=head;
        t=head->next;
        //1、寻找最大结点 
        while(t){
            if(t->data>max->data){
                max=t;
                premax=pret;
            }
            pret=t;
            t=t->next;    
        }
        //2、让这个结点离开原链表
        if(max==head)//如果找到的结点是第一个结点
            head=head->next;//让头指针向后移一位就行
        else
            premax->next=max->next;
        //3、把此结点头插法插入新链表
        max->next=head1;
        head1=max;
    }
    return head1;
}
int main()
{
    struct node *head,*p,*q;
    int i,n,a;
    scanf("%d",&n);
    head=NULL;
    for(i=0;i<n;i++){
        p=(struct node *)malloc(sizeof(struct node));
        scanf("%d",&a);
        p->data=a;
        p->next=NULL;
        if(!head){
            head=p;
        }else{
            q->next=p;
        }
        q=p;
    }
    printList(selectSort(head));
 }

时间复杂度分析

  • 选择排序时间复杂度是O(n^2)

直接插入排序

基本思想

  • 将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增加1的有序表。

代码实现

void InsertSort(int k[],int n)
{
	int i,j,temp;
	for(i=1;i<n;i++)
	{
	if(k[i]<k[i-1])
	{
		temp=k[i];
		for(j=i-1;k[j]>temp&&j>=0;j--)
		{
			k[j+1]=k[j];
		}
		k[j+1]=temp;
	}
	}
}

时间复杂度分析

  • 直接插入排序时间复杂度是O(n^2)

希尔排序(插入排序的一种)

基本思路

代码实现

void InsertSort(int k[],int n)
{
	int i,j,temp;
	int gap=n;
	do
	{
	gap=gap/3+1;
	for(i=gap;i<n;i++)
	{
	if(k[i]<k[i-gap])
	{
		temp=k[i];
		for(j=i-gap;k[j]>temp&&j>=0;j-=gap)
		{
			k[j+gap]=k[j];
		}
		k[j+gap]=temp;
	}
	}
	}while(gap>1);
}

时间复杂度分析

  • 希尔排序的时间复杂度是:O(nlogn)~O(n2),平均时间复杂度大致是O(n√n)

堆排序(完全二叉树)

堆排序的步骤

  1. 构造一个大根堆,取堆顶数字。
  2. 将剩下的数字构建一个大根堆,取堆顶数字。
  3. 重复以上操作,直到取完堆中的数字,最终得到一个从大到小的序列。
void swap(int k[],int i,int j)
{
	int temp;
	temp=k[i];
	k[i]=k[j];
	k[j]=temp;
}
void HeapAdjust(int k[],int s,int n)
{
	int i;
	int temp=k[s];
	for(i=2*s;i<=n;i*=2)
	{
		if(i<n&&k[i]<k[i+1])
		{
			i++;
		}
		if(temp>=k[i])
		{
			break;
		}
		k[s]=k[i];
		s=i;
		//swap(k,s,i);
	}
	k[s]=temp;
}
void HeapSort(int k[],int n)
{
	int i;
	for(int i=n/2;i>0;i--)
	{
		HeapAdjust(k,i,n);
	}
	for(i=n;i>1;i--)
	{
		swap(k,1,i);
		HeapAdjust(k,1,i-1);
	}
}
int main()
{
	int i,a[10]={-1,5,2,6,0,3,9,1,7,4};
	HeapSort(a,9);
	printf("jieguo:");
	for(i=1;i<10;i++)
	{
		cout<<a[i];
	}
	return 0;
}
  • 初始化建堆的时间复杂度为O(n),排序重建堆的时间复杂度为nlog(n),所以总的时间复杂度为O(n+nlogn)=O(nlogn)。另外堆排序的比较次数和序列的初始状态有关,但只是在序列初始状态为堆的情况下比较次数显著减少,在序列有序或逆序的情况下比较次数不会发生明显变化。
  • 基本操作:
    1. 查找最大值O(1)
    2. 插入一个数O(logn)
    3. 删除一个数O(logn)
    4. 修改一个数O(logn)

但是如果是c++ stl中的堆priority_queue,则不能修改一个数,而且只能删除堆顶元素。
堆的两个基本操作down()和up().

高精度

一般四种

  1. 大整数相加
  2. 大整数乘以比较小的数(大整数的位数为10的6次方,小整数大概为10的9次方大小)
  3. 大整数除以一个小整数
  4. 两个大整数相减
  • 用数组来存储,个位在数组的前面

高精度加法

代码实现

#include<iostream>
#include<vector>

using namespace std;
const int N=1e6+10;

vector<int> add(vector<int> &A,vector<int> &B)
{
    vector<int> C;
    
    int t=0;
    for(int i=0;i<A.size()||i<B.size();i++)
    {
       if(i<A.size()) t+=A[i];
       if(i<B.size()) t+=B[i];
       C.push_back(t%10);
       t/=10;
    }
    if(t) C.push_back(t);
    return C;
}

int main()
{
    string a,b;
    vector<int> A,B;
    cin>>a>>b;
    for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
    for(int i=b.size()-1;i>=0;i--) B.push_back(b[i]-'0');
    auto C=add(A,B);
    for(int i=C.size()-1;i>=0;i--) printf("%d",C[i]);
    return 0;
}

高精度减法

代码实现

#include<iostream>
#include<vector>

using namespace std;

bool cmp(vector<int> &A,vector<int> &B)
{
    if(A.size()!=B.size()) return A.size()>B.size();
    for(int i=A.size()-1;i>=0;i--)
    {
        if(A[i]!=B[i])
            return A[i]>B[i];
    }
    return true;
}

vector<int> sub(vector<int> &A,vector<int> &B)
{
    vector<int> C;
    int t=0;
    for(int i=0;i<A.size();i++)
    {
        t=A[i]-t;
        if(i<B.size()) t-=B[i];
        if(t>=0) 
        {
            C.push_back(t);
            t=0;
        }
        else 
        {
            C.push_back(t+10);
            t=1;
        }
    }
    while(C.size()>1&&C.back()==0) C.pop_back();
    return C;
}
int main()
{
    string a,b;
    vector<int> A,B;
    cin>>a>>b;
    for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
    for(int i=b.size()-1;i>=0;i--) B.push_back(b[i]-'0');
    if(cmp(A,B)) 
    {
        auto C=sub(A,B);
        int j=C.size()-1;
        
        for(int i=j;i>=0;i--) printf("%d",C[i]);
    }
    else
    {
        auto C=sub(B,A);
        printf("-");
        int j=C.size()-1;
        
        for(int i=j;i>=0;i--) 
        {
            printf("%d",C[i]);
        }
    }
    return 0;
}
  • ios::sync_with_stdio(false): C++中&
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值