计算机专业大一需要掌握的基本算法

a) 欧几里德算法求最大公约数

欧几里得算法

欧几里得算法

欧几里德算法也就是辗转相除法,有着2000年的历史了。欧几里德算法依据的算法理论是一个定理:gcd(a,b) = gcd(b,a mod b)

实现源码为:

//递归实现

int gcd(int m,int n)

{

       if (m < n)

       {

              int tmp = m;

              m = n;

              n = tmp;

       }

 

       if (n == 0)

              return m;

       else

              return gcd(n,m % n);

}

//非递归实现

int gcd2(int m,int n)

{

       if (m < n)

       {

              int tmp = m;

              m = n;

              n = tmp;

       }

 

       if (n == 0)

              return m;

      

       while (n > 0)

       {

              int tmp = m % n;

              m = n;

              n = tmp;

       }

 

       return m;

}

       这里给出了最大公约数的算法,那怎么求最大公倍数呢?其实知道了最大公约数,最小公倍数的求法就简单了:

int gbs(int m,int n)

{

       return m*n/gcd(m,n);

}


b) 筛法求素数

基本思想是:把从1开始的、某一范围内的正整数从小到大顺序排列, 1不是素数,首先把它筛掉。剩下的数中选择最小的数是素数,然后去掉它的倍数。依次类推,直到筛子为空时结束。

素数为0.. 合数为1..void getprim()
{
 int i ;
 for ( i = 2; i * i < N; i++)
 {
  if ( flag[i]) continue;
  for (int j = i; i * j < N; j++)
   flag[i*j] = 1;
 }
}


//用了筛法的方法:
#include<stdio.h>
#include<math.h>
#define N 10000001
bool prime[N];
int main()
{
 

 int i, j;
   for(i=2; i<N;i++)
  if(i%2) prime=false;
  else prime=true;
   for(i=3;i<=sqrt(N); i+=2)
    if(prime)
      for(j=i+i; j<N; j+=i)

           prime=false;
   }
   for(i=2; i<100;i++)//由于输出将占用太多io时间,所以只输出2-100内的素数。可以把100改为N
    if( prime )

 

       printf("%d ",i);
   return0;
}


1.普通求素数法

public int prime(int num){
   int n,m,i=0,s=0;
   label1:
   for(n=2;n<=num;n++)
   {
    for(m=2;m<=n/2;m++)
    {
     if(n%m==0)
     continue label1;
    }
    s++;
    i++;
    System.out.println("第"+i+"个素数是:"+n);
   }
   return s;
}

c) 康托展开

1.Cantor公式:
把一个整数X展开成如下形式:
X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[2]*1!+a[1]*0!
其中,a为整数,并且0<=a[i]<i(1<=i<=n)
举例

例如,3 5 7 4 1 2 9 6 8 展开为 98884。因为X=2*8!+3*7!+4*6!+2*5!+0*4!+0*3!+2*2!+0*1!+0*0!=98884.

解释:

排列的第一位是3,比3小的数有两个,以这样的数开始的排列有8!个,因此第一项为2*8!

排列的第二位是5,比5小的数有1、2、3、4,由于3已经出现,因此共有3个比5小的数,这样的排列有7!个,因此第二项为3*7!

以此类推,直至0*0!
用途

显然,n位(0~n-1)全排列后,其康托展开唯一且最大约为n!,因此可以由更小的空间来储存这些排列。由公式可将X逆推出唯一的一个排列。
康托展开的逆运算

既然康托展开是一个双射,那么一定可以通过康托展开值求出原排列,即可以求出n的全排列中第x大排列。

如n=5,x=96时:

首先用96-1得到95,说明x之前有95个排列.(将此数本身减去!)
用95去除4! 得到3余23,说明有3个数比第1位小,所以第一位是4.
用23去除3! 得到3余5,说明有3个数比第2位小,所以是4,但是4已出现过,因此是5.
用5去除2!得到2余1,类似地,这一位是3.
用1去除1!得到1余0,这一位是2.
最后一位只能是1.
所以这个数是45321.

按以上方法可以得出通用的算法。
康托展开:
int fac[] = {1,1,2,6,24,120,720,5040,40320,362880};
int cantor(int[] a, int k) {
int i, j, tmp, num = 0;
for (i = 0; i < k; i++) {
tmp = 0;
for (j = i + 1; j < k; j++)
if (a[j] < a[i])tmp++;
num += fac[k - i - 1] * tmp;
}
return num;
}
代码
逆康托展开:
int fac[] = {1,1,2,6,24,120,720,5040,40320,362880};
int[] uncantor(int x, int k) {
int res[] = new int[9];
int i, j, l, t;
boolean h[] = new boolean[12];
for (i = 1; i <= k; i++) {
t = x / fac[k - i];
x -= t * fac[k - i];
for (j = 1, l = 0; l <= t; j++)
if (!h[j])l++;
j--;
h[j] = true;
res[i - 1] = j;
}
return res;
}


e) 同余定理

两个整数ab,若它们除以正整数m所得的余数相等,则称ab对于模m同余

记作a \equiv b \pmod{m}

读作a同余于bm,或读作ab关于模m同余。

比如26 \equiv 14 \pmod{12}

同余于的符号是同余相等符号 。统一码值为 U+2261。但因为方便理由,人们有时会把它(误)写为普通等号 (=)。

整除性

a \equiv b \pmod{m} \Rightarrow c\cdot m=a-b, c \in \mathbb{Z} (即是说 a 和 b 之差是 m 的倍数)
换句话说,a \equiv b \pmod{m} \Rightarrow m \mid(a-b)[注 1]

传递性

\left. \begin{matrix}a \equiv b \pmod{m} \\b \equiv c \pmod{m}\end{matrix} \right\} \Rightarrow a \equiv c \pmod{m}

保持基本运算

\left. \begin{matrix}a \equiv b \pmod{m} \\c \equiv d\pmod{m}\end{matrix} \right\} \Rightarrow \left\{ \begin{matrix} a \pm c \equiv b \pm d \pmod{m} \\ ac \equiv bd \pmod{m} \end{matrix} \right.
这性质更可进一步引申成为这样:
a \equiv b \pmod{m} \Rightarrow \begin{cases} an \equiv bn \pmod{m}, \forall n \in \mathbb{Z} \\ a^n \equiv b^n \pmod{m}, \forall n \in \mathbb{N}^0\end{cases}

除法原理

a \equiv b \pmod{cn} \Rightarrow a \equiv b \pmod n
\left. \begin{matrix} a \equiv b \pmod{m} \\ n|m \end{matrix} \right\} \Rightarrow a \equiv b \pmod n[注 1]
\left. \begin{matrix} ac \equiv bc \pmod{m} \\ (c, m) = 1 \end{matrix} \right\} \Rightarrow a \equiv b \pmod m[注 2]

\left. \begin{matrix} a \equiv b \pmod{m_1} \\ a \equiv b \pmod{m_2} \\ \vdots \\ a \equiv b \pmod{m_n} \\ (n \ge 2) \end{matrix} \right\} \Rightarrow a \equiv b \pmod{[m_1,m_2,\cdots,m_n]}[注 3]

例子

  • 自然数a的个位数字,就是求a与哪一个数对于模10同余。
  • 10\equiv 1 (\textrm{mod }\ 3), 10^{n}\equiv 1 (\textrm{mod }\ 3), 10001\equiv 10^{4}+1\equiv 1+1 (\textrm{mod }\ 3)

f) 次方求模


  
比如a的b次方对c求模
我们可以把b 化为二进制形式看那一位有1
比如b=10101则  a^b=a^(10000)*a^(100)*a^(1)
以函数形式体现:
long long a,b,c;
void han()
{
    long long t,s;
    for(t=a,s=1;b;b>>=1,t*=t,t%=c)//用b>>=1查看b中1
     if(b&1){s*=t;s%=c;}
    printf("%lld\n",s%c); 
}


  

3. 计算几何初步

a) 三角形面积

  if(a+b>c&&a+c>b&&c+b>a)
    {p=(a+b+c)/2;
    p=p*(p-a)*(p-b)*(p-c);
    s=sqrt(p);
    printf("%f",s);}
    else printf("你输入的三边,不能构成三角形");
    return 0;

b) 三点顺序

利用矢量叉积判断是逆时针还是顺时针。

      设A(x1,y1),B(x2,y2),C(x3,y3),则三角形两边的矢量分别是:
      AB=(x2-x1,y2-y1), AC=(x3-x1,y3-y1)
      则AB和AC的叉积为:(2*2的行列式)
      |x2-x1, y2-y1|
      |x3-x1, y3-y1|
      值为:(x2-x1)*(y3-y1) - (y2-y1)*(x3-x1)

      利用右手法则进行判断:
      如果ABxAC>0,则三角形ABC是逆时针的;
      如果ABxAC<0,则三角形ABC是顺时针的;
    如果ABxAC=0,则说明三点共线。

4. 学会简单计算程序的时间复杂度与空间复杂度

5. 二分查找法

void bisearch(int a[],int n,int x)
{
 int low,mid,high;
 int i;
 low=0;
 high=n-1;
 int succeed=0;
 while(low<=high)
 {
         mid=(low+high)/2;
   if(x==a[mid])
   {
    succeed=1;
    printf("折半查找成功 \n此数值%d在数组里第%d个位置\n",x,mid+1);
    break;
   }
   if(x>a[mid])
   {
    low=mid+1;
   }
   if(x<a[mid])
   {
    high=mid-1;
   }
 }
 if(succeed==0)
  printf("折半查找失败 即数值%d不存在于数组里\n");
 
}

6. 简单的排序算法

/*
** 常见排序算法比较
*/

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
#define N 10
#define Demo 1

void BubbleSort(int arr[], int n);
void SelectSort(int arr[], int n);
void QuickSort(int arr[], int n);
void PrintArray(int arr[], int n);
void GenerateArray(int arr[], int n);

int main(int argc, char *argv[])
{
    int arr[N];
       
    GenerateArray(arr, N);
    #if Demo
    printf("Before the bubble sort------------------------\n");
    PrintArray(arr, N);
    #endif
    printf("Start Bubble sort----------------------\n");
    clock_t start_time1=clock(); //开始计时
    BubbleSort(arr, N);
    clock_t end_time1=clock(); // 结束计时
    printf("Running time is: %lf ms\n", (double)(end_time1-start_time1)/CLOCKS_PER_SEC*1000); //输出运行时间
    #if Demo
    printf("After the bubble sort------------------------\n");
    PrintArray(arr, N);
    #endif
    printf("-----------------------------------------------------------\n");
   
    sleep(1000); // 单位是毫秒即千分之一秒
    GenerateArray(arr, N);
    #if Demo
    printf("Before the selection sort------------------------\n");
    PrintArray(arr, N);
    #endif
    printf("Start selection sort----------------------\n");
    clock_t start_time2=clock(); //开始计时
    SelectSort(arr, N);
    clock_t end_time2=clock(); // 结束计时
    printf("Running time is: %lf ms\n", (double)(end_time2-start_time2)/CLOCKS_PER_SEC*1000); //输出运行时间
    #if Demo
    printf("After the selection sort------------------------\n");
    PrintArray(arr, N);
    #endif
   
    printf("-----------------------------------------------------------\n");
    sleep(1000); // 单位是毫秒即千分之一秒
    GenerateArray(arr, N);
    #if Demo
    printf("Before the quick sort------------------------\n");
    PrintArray(arr, N);
    #endif
    printf("Start quick sort----------------------\n");
    clock_t start_time3=clock(); //开始计时
    QuickSort(arr, N);
    clock_t end_time3=clock(); // 结束计时
    printf("Running time is: %lf ms\n", (double)(end_time3-start_time3)/CLOCKS_PER_SEC*1000); //输出运行时间
    #if Demo
    printf("After the quick sort------------------------\n");
    PrintArray(arr, N);
    #endif
 
  system("PAUSE");
  return 0;
}

// 产生随机列表
void GenerateArray(int arr[], int n)
{
     int i;
     srand((unsigned)time(0));
    
     for(i = 0; i <N; i++)
     {
          arr[i] = rand(); // 生成随机数 范围在0-32767之间
     }
}

// 打印列表
void PrintArray(int arr[], int n)
{
     int i = 0;
     for(i = 0; i < n; i++)
           printf("%6d", arr[i]);
     printf("\n");
}

// 经典冒泡排序
void BubbleSort(int arr[], int n)
{
     int i = 0, j =0;    
     for(i = 0; i < n; i++)
       for(j = 0; j < n - 1 - i; j++)
       {
             if(arr[j] > arr[j + 1])
             {
                       arr[j] = arr[j] ^ arr[j+1];
                       arr[j+1] = arr[j] ^ arr[j+1];
                       arr[j] = arr[j] ^ arr[j+1];
             }            
       }    
}

// 快速排序的递归实现
void QuickSort(int arr[], int n)
{
     if(n <= 1)
     return;
    
     int i =0 , j = n - 1;
     int key = arr[0];
     int index = 0;
    
     while(i < j)
     {
             // 从后向前搜索
             while(j > i && arr[j] >= key)
             j--;
             if(j == i)
             break;
             else
             {
                 //交换 a[j] a[i]
                 arr[j] = arr[j] ^arr[i];
                 arr[i] = arr[j] ^arr[i];
                 arr[j] = arr[j] ^arr[i];
                 index = j;
             }
            
             // 从前向后搜索
             while(i < j && arr[i] <= key)
             i++;
             if(i == j)
             break;
             else
             {
                 // 交换 a[i] a[j]
                 arr[j] = arr[j] ^arr[i];
                 arr[i] = arr[j] ^arr[i];
                 arr[j] = arr[j] ^arr[i];
                 index = i;
             }            
     }    
     QuickSort(arr, index);
     QuickSort(arr + index + 1, n - 1 - index);
}

// 选择排序
void SelectSort(int arr[], int n)
{
     int i, j;
     int min;
    
     for(i = 0; i < n - 1; i++)
     {
           int index = 0;
           min = arr[i];
           for(j = i + 1; j < n; j++) //找出 i+1 - n 无序区的最小者与arr[i]交换
           {
                 if(arr[j] < min)
                 {
                    min = arr[j];
                    index = j;     
                 }  
           }
           if(index != 0) //表明无序区有比arr[i]小的元素
           {
               arr[i] = arr[i]^arr[index];
               arr[index] = arr[i]^arr[index];
               arr[i] = arr[i]^arr[index];
           }
     }
}

 

程序里有几点注意的地方:

一,在程序里,交换2个数,我使用了异或来处理。这个可以根据个人喜好。为了避免产生临时变量,可以使用如下几种方式来交换2个数:

  a=a^b; 
  b=a^b; 
  a=a^b; 

 

或者
  a=a+b; 
  b=a-b; 
  a=a-b; 

使用第二种也挺好的。第一种异或的方式,只适用于,2个数都为int型的,a,b可以正可以负,这个没有关系,但是必须是int类型。

二, sleep()函数是包含在windows.h里面的,要加入 #include <window.h>

三, 关于随机数生成的2个函数 srand()种子发生器函数,还有rand()随机数生成器函数,自己可以参考相关文档。

四, Demo宏来控制是演示还是比较性能用的。当把N调整的很小,比如10的时候,可以设置Demo为1,那样就能打印数组了,可以看到比较前后的情况。当把N调整到很大比如10000的时候,就把Demo设置为0,那样就不打印数组,直接比较性能。


7. 贪心算法经典题目

顾名思义,贪心算法总是作出在当前看来最好的选择。也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。当然,希望贪心算法得到的最终结果也是整体最优的。虽然贪心算法不能对所有问题都得到整体最优解,但对许多问题它能产生整体最优解。如单源最短路经问题,最小生成树问题等。在一些情况下,即使贪心算法不能得到整体最优解,其最终结果却是最优解的很好近似。

问题一、活动安排问题

问题表述:设有n个活动的集合E = {1,2,…,n},其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。每个活i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si < fi 。如果选择了活动i,则它在半开时间区间[si, fi)内占用资源。若区间[si, fi)与区间[sj, fj)不相交,则称活动i与活动j是相容的。也就是说,当si >= fj或sj >= fi时,活动i与活动j相容。

由于输入的活动以其完成时间的非减序排列,所以算法greedySelector每次总是选择具有最早完成时间的相容活动加入集合A中。直观上,按这种方法选择相容活动为未安排活动留下尽可能多的时间。也就是说,该算法的贪心选择的意义是使剩余的可安排时间段极大化,以便安排尽可能多的相容活动。

算法greedySelector的效率极高。当输入的活动已按结束时间的非减序排列,算法只需O(n)的时间安排n个活动,使最多的活动能相容地使用公共资源。如果所给出的活动未按非减序排列,可以用O(nlogn)的时间重排。

例:设待安排的11个活动的开始时间和结束时间按结束时间的非减序排列如下:

算法greedySelector 的计算过程如下图所示。图中每行相应于算法的一次迭代。阴影长条表示的活动是已选入集合A的活动,而空白长条表示的活动是当前正在检查相容性的活动。

若被检查的活动i的开始时间Si小于最近选择的活动j的结束时间fi,则不选择活动i,否则选择活动i加入集合A中。 

贪心算法并不总能求得问题的整体最优解。但对于活动安排问题,贪心算法greedySelector却总能求得的整体最优解,即它最终所确定的相容活动集合A的规模最大。这个结论可以用数学归纳法证明。

活动安排问题实现:

贪心算法的基本要素

对于一个具体的问题,怎么知道是否可用贪心算法解此问题,以及能否得到问题的最优解呢?这个问题很难给予肯定的回答。

但是,从许多可以用贪心算法求解的问题中看到这类问题一般具有2个重要的性质:贪心选择性质和最优子结构性质。

1、贪心选择性质

所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。

动态规划算法通常以自底向上的方式解各子问题,而贪心算法则通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。 

对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解。

2、最优子结构性质

当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。问题的最优子结构性质是该问题可用动态规划算法或贪心算法求解的关键特征。 

3、贪心算法与动态规划算法的差异

贪心算法和动态规划算法都要求问题具有最优子结构性质,这是2类算法的一个共同点。但是,对于具有最优子结构的问题应该选用贪心算法还是动态规划算法求解?是否能用动态规划算法求解的问题也能用贪心算法求解?下面研究2个经典的组合优化问题,并以此说明贪心算法与动态规划算法的主要差别。

0-1背包问题:

给定n种物品和一个背包。物品i的重量是Wi,其价值为Vi,背包的容量为C。应如何选择装入背包的物品,使得装入背包中物品的总价值最大?

在选择装入背包的物品时,对每种物品i只有2种选择,即装入背包或不装入背包。不能将物品i装入背包多次,也不能只装入部分的物品i。

背包问题:

与0-1背包问题类似,所不同的是在选择物品i装入背包时,可以选择物品i的一部分,而不一定要全部装入背包,1 <= i <= n。

这2类问题都具有最优子结构性质,极为相似,但背包问题可以用贪心算法求解,而0-1背包问题却不能用贪心算法求解。

用贪心算法解背包问题的基本步骤:

首先计算每种物品单位重量的价值Vi/Wi,然后,依贪心选择策略,将尽可能多的单位重量价值最高的物品装入背包。若将这种物品全部装入背包后,背包内的物品总重量未超过C,则选择单位重量价值次高的物品并尽可能多地装入背包。依此策略一直地进行下去,直到背包装满为止。

void Knapsack(int n,float M,float v[],float w[],float x[])

  Sort(n,v,w);

  int i;

  for (i = 1 ; i <= n ; i++) 

    x[i] = 0;

    float c=M;

    for (i=1;i<=n;i++) {

      if (w[i] > c) break;

    x[i]=1;

    c-=w[i];

  if (i <= n) 

    x[i]=c / w[i];

算法knapsack的主要计算时间在于将各种物品依其单位重量的价值从大到小排序。因此,算法的计算时间上界为 O(nlogn)。

为了证明算法的正确性,还必须证明背包问题具有贪心选择性质。

对于0-1背包问题,贪心选择之所以不能得到最优解是因为在这种情况下,它无法保证最终能将背包装满,部分闲置的背包空间使每公斤背包空间的价值降低了。事实上,在考虑0-1背包问题时,应比较选择该物品和不选择该物品所导致的最终方案,然后再作出最好选择。由此就导出许多互相重叠的子问题。这正是该问题可用动态规划算法求解的另一重要特征。实际上也是如此,动态规划算法的确可以有效地解0-1背包问题。

问题二、 哈夫曼编码

哈夫曼编码是广泛地用于数据文件压缩的十分有效的编码方法。其压缩率通常在20%~90%之间。哈夫曼编码算法用字符在文件中出现的频率表来建立一个用0,1串表示各字符的最优表示方式。

给出现频率高的字符较短的编码,出现频率较低的字符以较长的编码,可以大大缩短总码长。

  3*(45+13+12+16+9+5) = 300 千位

  1*45+3*13+3*12+3*16+4*9+4*5 = 224 千位

1、前缀码

对每一个字符规定一个0,1串作为其代码,并要求任一字符的代码都不是其它字符代码的前缀。这种编码称为前缀码

编码的前缀性质可以使译码方法非常简单。 

表示最优前缀码的二叉树总是一棵完全二叉树,即树中任一结点都有2个儿子结点。

f(c)表示字符c出现的概率,dt(c)表示c的码长

平均码长定义为:

使平均码长达到最小的前缀码编码方案称为给定编码字符集C的最优前缀码

2、构造哈夫曼编码

哈夫曼提出构造最优前缀码的贪心算法,由此产生的编码方案称为哈夫曼编码

哈夫曼算法以自底向上的方式构造表示最优前缀码的二叉树T。

算法以|C|个叶结点开始,执行|C|-1次的“合并”运算后产生最终所要求的树T。 

以f为键值的优先队列Q用在贪心选择时有效地确定算法当前要合并的2棵具有最小频率的树。一旦2棵具有最小频率的树合并后,产生一棵新的树,其频率为合并的2棵树的频率之和,并将新树插入优先队列Q。经过n-1次的合并后,优先队列中只剩下一棵树,即所要求的树T。

算法huffmanTree用最小堆实现优先队列Q。初始化优先队列需要O(n)计算时间,由于最小堆的removeMin和put运算均需O(logn)时间,n-1次的合并总共需要O(nlogn)计算时间。因此,关于n个字符的哈夫曼算法的计算时间为O(nlogn) 。

3、哈夫曼算法的正确性

要证明哈夫曼算法的正确性,只要证明最优前缀码问题具有贪心选择性质和最优子结构性质。

(1)贪心选择性质

(2)最优子结构性质

问题三、单源最大路径

给定带权有向图G =(V,E),其中每条边的权是非负实数。另外,还给定V中的一个顶点,称为源。现在要计算从源到所有其它各顶点的最短路长度。这里路的长度是指路上各边权之和。这个问题通常称为单源最短路径问题。

1、算法基本思想

Dijkstra算法是解单源最短路径问题的贪心算法。

其基本思想是,设置顶点集合S并不断地作贪心选择来扩充这个集合。一个顶点属于集合S当且仅当从源到该顶点的最短路径长度已知。

初始时,S中仅含有源。设u是G的某一个顶点,把从源到u且中间只经过S中顶点的路称为从源到u的特殊路径,并用数组dist记录当前每个顶点所对应的最短特殊路径长度。Dijkstra算法每次从V-S中取出具有最短特殊路长度的顶点u,将u添加到S中,同时对数组dist作必要的修改。一旦S包含了所有V中顶点,dist就记录了从源到所有其它顶点之间的最短路径长度。

例如,对下图中的有向图,应用Dijkstra算法计算从源顶点1到其它顶点间最短路径的过程列在下表中。

Dijkstra算法的迭代过程:

   

dist[2]

dist[3]

dist[4]

dist[5]

    

maxint

  
 

{1,2}

     
 

{1,2,4}

     
 

{1,2,4,3}

     
 

{1,2,4,3,5}

     

2、算法的正确性和计算复杂性

(1)贪心选择性质

(2)最优子结构性质

(3)计算复杂性

对于具有n个顶点和e条边的带权有向图,如果用带权邻接矩阵表示这个图,那么Dijkstra算法的主循环体需要O(n)时间。这个循环需要执行n-1次,所以完成循环需要O(n)时间。算法的其余部分所需要时间不超过O(n^2)。

参考资料 《算法分析与设计》 王晓东编著


作为一名准大一计算机专业的学生,你可以从以下几个方面入手学习: 1. **基础知识**:了解计算机科学的基础理论,包括数据结构、算法、计算机组成原理、操作系统等。这些都是后续课程的基础,通过书籍、在线教程或课堂学习掌握。 2. **编程语言**:开始学习至少一种编程语言,比如C/C++、Python或Java。选择一门流行的,并广泛应用于实际项目中的语言。通过编写小项目来实践。 3. **软件工程**:理解软件开发的基本流程,包括需求分析、设计、编码、测试和维护。版本控制工具(如Git)、持续集成/持续部署(CI/CD)也是重要技能。 4. **计算机网络**:学习TCP/IP协议栈,理解网络通信原理,熟悉HTTP、FTP等常用协议。 5. **数据库基础**:了解SQL语言,学习基本的数据库管理系统(DBMS),如MySQL、Oracle或MongoDB。 6. **数学基础**:虽然不是必需的,但对线性代数、概率论和统计学有一定程度的理解,有助于处理复杂的数据结构和算法问题。 7. **动手实践**:参加一些开源项目、编程竞赛或者创建个人项目,这能提高编程能力和解决问题的能力。 8. **阅读与研究**:关注行业动态和技术趋势,阅读技术博客、论文或者参与技术社区讨论,保持好奇心。 记得,大学期间不仅是知识的学习,还有团队合作、自学能力的培养和职业规划,不断探索并适应新技术。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值