Partage
  • Partager sur Facebook
  • Partager sur Twitter

Algorithmes divers multi-langage

tris, pathfinder, calcul, matrice, etc.

    3 novembre 2010 à 22:03:53

    Citation : khdra

    meci


    menn
    • Partager sur Facebook
    • Partager sur Twitter
      7 novembre 2010 à 20:45:59

      Spéciale dédicace, le PGCD :

      pgcd =: [: {. |/\@|.^:(*@{:)^:_
      • Partager sur Facebook
      • Partager sur Twitter
        7 novembre 2010 à 21:30:49

        @Pouet-forever : En quel langage est-ce ?
        • Partager sur Facebook
        • Partager sur Twitter
          1 mars 2011 à 12:27:32

          Hello !


          Un petit AStar en C++, fait assez rapidement :)

          //
          // Code by Yann Viens
          // Started 18/01/2011
          // AStar.h: Class AStar implements PathFinder interface
          //
          
          #ifndef ASTAR_H_
          # define ASTAR_H_
          
          # include "PathFinder.h"
          
          // Code for some methods is defined in the header file so they are inline
          
          class	AStar: public PathFinder
          {
           public:
            AStar();
            ~AStar();
          
            void	setStart(const int start)
            {
              _start = start;
            }
            void	setEnd(const int end)
            {
              _end = end;
            }
          
            std::list<int> const&	findPath(Graph& graph) throw (NoPathFound);
          
           private:
            int	_start;
            int	_end;
          
            struct		HeurNode
            {
              HeurNode();
              HeurNode(const float heur, const float dis, const int id, const int prev);
              float		_heur;
              float		_dis;
              int			_id;
              int			_prev;
            };
            typedef std::list<HeurNode>		WorkList;
          
            WorkList		_openSet;
            WorkList		_closeSet;
          
            void			processVertex(Graph::tNode & nodes, HeurNode& toVisit);
            float			calcDist(Node const& actual, Node const& toGo);
            bool			isInWorkList(const int id, WorkList const& daList);
            void			insertInOpenList(int id, int prev, float heur, float dis, WorkList& daList);
            std::list<int>const&	buildBackWay();
          
          
          };
          
          #endif // ASTAR_H_
          


          //
          // Code by Yann Viens
          // Started 18/01/2011
          // AStar.cpp : Implementation of AStar class
          //
          
          #include <cmath>
          #include "AStar.h"
          
          AStar::AStar()
          {
          }
          
          AStar::~AStar()
          {
          }
          
          AStar::HeurNode::HeurNode()
          {
          }
          
          AStar::HeurNode::HeurNode(const float heur, const float dis, const int id, const int prev): _heur(heur), _dis(dis), _id(id), _prev(prev)
          {
          }
          
          //
          // Check if a node is already in a list (open or close set)
          //
          bool			AStar::isInWorkList(const int id, WorkList const& daList)
          {
            WorkList::const_iterator		itListBegin = daList.begin();
            const WorkList::const_iterator	itListEnd = daList.end();
          
            while (itListBegin != itListEnd)
              {
                if ((*itListBegin)._id == id)
          	return true;
                ++itListBegin;
              }
            return false;
          }
          
          //
          // Insert a new node in the open set. The open set is sorted to optimize the algorithm.
          //
          void			AStar::insertInOpenList(const int id, const int prev, const float heur, const float dis, WorkList& daList)
          {
            WorkList::iterator		itListBegin = daList.begin();
            const WorkList::iterator	itListEnd = daList.end();
          
            while (itListBegin != itListEnd && (*itListBegin)._heur < heur)
              ++itListBegin;
            if (itListBegin != itListEnd)
              daList.insert(itListBegin, HeurNode(heur, dis, id, prev));
            else
              daList.push_back(HeurNode(heur, dis, id, prev));
          }
          
          //
          // Calcul the real distance between two nodes
          //
          float			AStar::calcDist(Node const& actual, Node const& toGo)
          {
            return sqrt(pow(toGo.getPosX() - actual.getPosX(), 2) + pow(toGo.getPosY() - actual.getPosY(), 2) + pow(toGo.getPosZ() - actual.getPosZ(), 2));
          }
          
          //
          // This method is called to run through the nodes
          //
          void			AStar::processVertex(Graph::tNode & nodes, HeurNode& visiting)
          {
            std::list<int>const&	adj = nodes[visiting._id]->getAdjList();
            std::list<int>::const_iterator	itAdjBegin = adj.begin();
            const std::list<int>::const_iterator	itAdjEnd = adj.end();
            float					heuristic, dis;
          
            while (itAdjBegin != itAdjEnd)
              {
                if (isInWorkList(*itAdjBegin, _openSet) == false && isInWorkList(*itAdjBegin, _closeSet) == false)
          	{
          	  // Heuristic calcul: distance between the next node and the end
          	  heuristic = calcDist(*nodes[*itAdjBegin], *nodes[_end]);
          	  dis = visiting._dis + calcDist(*nodes[visiting._id], *nodes[*itAdjBegin]);
          	  insertInOpenList(*itAdjBegin, visiting._id, heuristic, dis, _openSet);
          	}
                ++itAdjBegin;
              }
          }
          
          //
          // When the end node is found, this methods is called to build back the way between the start and the end
          //
          std::list<int>const&	AStar::buildBackWay()
          {
            std::list<int>*	way = new std::list<int>();
            HeurNode		back = _closeSet.front();
            WorkList::iterator	itListBegin;
            WorkList::iterator	itListEnd;
          
            while (back._id != _start)
              {
                way->push_front(back._id);
                itListBegin = _closeSet.begin();
                itListEnd = _closeSet.end();
                while (itListBegin != itListEnd && (*itListBegin)._id != back._prev)
          	++itListBegin;
                back = *itListBegin;
              }
            way->push_front(back._id);
            return *way;
          }
          
          //
          // Method to call to build a way from a graph
          //
          std::list<int>const&	AStar::findPath(Graph& graph) throw (NoPathFound)
          {
            Graph::tNode const&	nodes = graph.getNodes();
          
            if (nodes.find(_start) == nodes.end() || nodes.find(_end) == nodes.end())
              throw NoPathFound(_start, _end);
          
            HeurNode		toVisit;
            bool			found = false;
          
            _openSet.clear();
            _closeSet.clear();
            _openSet.push_front(HeurNode(0, 0, _start, 0));
            while (_openSet.size() && found == false)
              {
                toVisit = _openSet.front();
                _openSet.pop_front();
          
                _closeSet.push_front(toVisit);
                if (toVisit._id == _end)
          	found = true;
                else
          	processVertex(const_cast<Graph::tNode&>(nodes), toVisit);
              }
            if (found == false)
              throw (NoPathFound(_start, _end));
            return buildBackWay();
          }
          


          //
          // Code by Yann Viens
          // Started 18/01/2011
          // PathFinder.h: Class PathFinder is an interface for path finding algorithms
          //
          
          
          #ifndef PATHFINDER_H_
          # define PATHFINDER_H_
          
          # include <list>
          # include "Graph.h"
          # include "Exception/NoPathFound.h"
          
          class	PathFinder
          {
           public:
            virtual ~PathFinder() {}
            virtual void			setStart(const int start) = 0;
            virtual void			setEnd(const int start) = 0;
            virtual std::list<int>const&	findPath(Graph& graph) throw (NoPathFound) = 0;
          };
          
          #endif // PATHFINDER_H_
          


          //
          // Code by Yann Viens
          // Started 17/01/2011
          // Graph.h: Class Graph is class used to defined a graph
          // In this program, a graph is defined by a map of vertex (tNode)
          // Each vertex (or node), contains a list of adjacent vertex
          //
          
          #ifndef GRAPH_H_
          # define GRAPH_H_
          
          # include <map>
          # include "Node.h"
          
          // Code for some methods is defined in the header file so they are inline
          
          class	Graph
          {
           public:
            Graph();
            ~Graph();
          
            typedef std::map<int, Node*>	tNode;
          
            void			addNode(Node* newNode)
            {
              _nodes[newNode->getId()] = newNode;
            }
            tNode const &		getNodes() const
            {
              return _nodes;
            }
          
            void			addLink(const int id, const int adjVertex);
          
          #ifdef _PATHFINDERDEBUG
            void			displayGraph() const;
          #endif
          
           private:
            tNode		_nodes;
          };
          
          #endif // !GRAPH_H_
          


          //
          // Yann Viens
          // Started 17/01/2011
          // Node.h: A node represents a Vertex in a graph
          // Each Node has an adjacent vertex list, that describe the adjacents roads
          //
          
          #ifndef NODE_H_
          # define NODE_H_
          
          # include <list>
          # include "Room.h"
          
          // Code for some methods is defined in the header file so they are inline
          
          class	Node: public Room
          {
           public:
            Node();
            ~Node();
          
            void				addAdjVertex(const unsigned int id)
            {
              _adjList.push_back(id);
            }
          
            // Getter & Setter
            int	getId() const
            {
              return _id;
            }
            void	setId(const int id)
            {
              _id = id;
            }
          
            std::list<int>const&	getAdjList() const
              {
                return _adjList;
              }
          
           private:
            int			_id;
            std::list<int>	_adjList;
          };
          
          #endif // !NODE_H_
          


          J'ai mis ici uniquement le code des structures de données utilisées, et celui de l'algo. Si vous voulez un exemple concret d'utilisation de cet algo, vous en trouverez un ici : http://yannshu.com/code/cpp/PathFinder-Bin+Code.zip
          Cet exemple contient un exécutable compilé pour windows, ainsi que la totalité du code source.
          • Partager sur Facebook
          • Partager sur Twitter
          Découvrez un petit jeu Android bien sympa : http://www.siteduzero.com/forum/sujet/appli-jeu-android-cube-racer
            20 mars 2011 à 11:40:48

            File de priorité - avec itérateurs (C++)



            Bonjour,

            Ayant eu besoin d'une file de priorité itérable en C++, ce qui n'est pas possible avec le conteneur <priority_queue>, voici ma mouture personnelle :

            #ifndef _P_QUEUE_H
            #define _P_QUEUE_H
            
            
            #include <vector>
            #include <algorithm>
            
            
            template <typename T>
            class p_queue : private std::vector<T>
            {
            public:
                typedef typename std::vector<T>::const_iterator const_iterator;
            
                p_queue();
            
                void push (const T& x)  { std::vector<T>::push_back(x); push_heap( std::vector<T>::begin(), std::vector<T>::end() ); }
                void pop ()             { pop_heap( std::vector<T>::begin(), std::vector<T>::end() ); std::vector<T>::pop_back(); }
            
                inline const T& top() const { return std::vector<T>::front(); }
                inline bool empty() const   { return std::vector<T>::empty(); }
                inline size_t size() const  { return std::vector<T>::size(); }
            
                inline const_iterator begin()     { return std::vector<T>::begin(); }
                inline const_iterator end()       { return std::vector<T>::end(); }
            };
            
            
            #endif
            


            Le principe est très simple : on réutilise le conteneur <vector>, que l'on utilise conjointement avec les fonction push_heap() et pop_heap() (définies dans <algorithm>) pour gérer la file de priorité.

            Et enfin, un typedef pour redéfinir les itérateurs. Seuls les const_iterator sont fournis, pour que l'utilisateur ne puisse pas corrompre le heap.
            • Partager sur Facebook
            • Partager sur Twitter
              21 mars 2011 à 23:46:46

              Sympa ;)
              Ça me rappelle une std::stack mutante itérable que j'avais codé il y a quelques temps.. J'vais essayer de retrouver ça !


              Par contre, ajouter des éléments dans un vector avec des push_back, c'est pas génial il me semble...

              Citation : http://www.cplusplus.com/reference/stl/vector/push_back/

              This effectively increases the vector size by one, which causes a reallocation of the internal allocated storage if the vector size was equal to the vector capacity before the call.

              • Partager sur Facebook
              • Partager sur Twitter
              Découvrez un petit jeu Android bien sympa : http://www.siteduzero.com/forum/sujet/appli-jeu-android-cube-racer
                22 mars 2011 à 8:41:58

                Citation : Yannshu

                Par contre, ajouter des éléments dans un vector avec des push_back, c'est pas génial il me semble...



                Comprends pas le problème, avec quoi tu voudrais ajouter des éléments (un par un) sinon ?

                Et puis, les réallocation sont censées arriver plutôt rarement :

                Citation : http://www.cplusplus.com/reference/stl/vector/push_back/

                This effectively increases the vector size by one, which causes a reallocation of the internal allocated storage if the vector size was equal to the vector capacity before the call.


                • Partager sur Facebook
                • Partager sur Twitter
                  23 mars 2011 à 13:43:25

                  C'est du O(1) en amorti le push_back.
                  • Partager sur Facebook
                  • Partager sur Twitter
                    10 avril 2011 à 21:37:23

                    Bonjour j'aimerai proposer ma propre version du quicksort en Ocaml en version list. Elle ressemble beaucoup a celle déjà faites mais bon je veut quand même partager :p

                    Quicksort

                    let rec  partition l p=
                      match l with
                        | [] -> ([],[])
                        |a::q when a<p ->
                          let (x,y)= partition q p in
                          (a::x,y)
                        |a::q -> let (x,y)= partition q p in
                          (x,a::y);;
                    
                    let rec quicksort l =
                      if l=[] then []
                      else let a =List.hd l and q = List.tl l in
                           let x,y = partition q a in
                           (quicksort x)@(a::(quicksort y));;
                    


                    Crible d'Erathostène

                    Il a été écrit en C et je l'ai pas vu en Ocaml alors le voila Pour l'explication vous pouvez aller voir le premier post sur ce sujet. La grande différence c'est que j'utilise True et False au lieu de chiffre dans mon tableau





                    let crible n =
                      let t = Array.make (n+1) true in
                      for i = 2 to n/2 do (*racine de n marche mieux (en complexité) car a partir de racine de n, k est tj plus grand que n et donc la suite est inutile *)
                          if t.(i) then
                    	( 
                    	let k = ref (i*i) in
                    	while !k <= n do
                    	  t.(!k)<- false;
                    	  k:= !k +i;
                    	done;
                    	) 
                      done;                     (* la c'est fini il reste plus qu'a les afficher *)
                      for i = 2 to n do 
                        if t.(i) then (print_int(i);print_string" ";)
                      done;;
                    



                    Ok puisque j'y suis voici un Quicksort avec des vecteur en Ocaml

                    let partition t d f =
                      let i = ref d and j = ref f in 
                      while !i < !j do
                        if t.(!i+1) <= t.(!i) then 
                          ( 
                    	swap t !i (!i+1);  
                    	incr i; 
                          ) 
                        else 
                          (    
                    	swap t (!i+1) !j;
                    	decr j;
                          )
                      done;
                    !i;;
                    
                    
                    let quicksort t =
                      let rec aux d f =
                        if d < f then
                        let i = partition t d f in
                        aux d (i-1);
                        aux (i+1) f;
                      in 
                      aux 0 ((Array.length t)-1);;
                    
                    • Partager sur Facebook
                    • Partager sur Twitter
                      23 juillet 2011 à 0:08:24

                      Pathfinder : Dijkstra


                      Principe



                      L'algorithme de Dijkstra (du nom de son inventeur) permet de parcourir un graphe quelconque pondéré (à la différence du BFS) et d'en déduire le plus court chemin d'un noeud A à tous les autres noeuds du graphe.

                      Le principe de l'algorithme est le suivant, pour chaque noeud considéré (le premier étant le noeud de départ) on ajoute ses voisins (si ils n'ont pas déjà été vus) dans une structure qui va nous permettre de déterminer efficacement lequel de tous les noeuds ajoutés non visité est le plus proche.

                      La structure la mieux adaptée pour répondre à cette question est un tas (ou file à priorité), on code un tas en utilisant un arbre binaire ou tout simplement on se sert du conteneur prioirty_queue de la stl prévu à cet effet.

                      L'algorithme s'arrête quand tous les noeuds du graphe ont été visités (ou quand le noeud d'arrivé est atteint, selon implémentation).

                      Attention, les pondérations se doivent d'être positives.

                      Complexité



                      On considérera qu'un noeud contient comme information sa distance au noeud de départ.

                      Le pseudo code de notre algorithme est le suivant :

                      Ajoute noeud de départ dans tas

                      Tans que le tas n'est pas vide
                      |noeud_courrant = sommet du tas
                      |enleve noeud du tas
                      |
                      |Pour chaque voisin
                      ||Si le voisin n'a jamais été vu
                      |||Marquer le voisin à vu
                      |||noeud_voisin.distance = noeud_courrant.distance + poids_arête[noeud_courrant][noeud_voisin]
                      |||distance_noeud_depart[noeud_voisin] = noeud_voisin.distance
                      |||Ajouter le noeud_voisin au tas

                      Les opérations d'ajout dans le tas se font en ln(nbNoeuds) et on passera par chaque arc une fois, soit une compléxité de :

                      O((nbNoeuds + nbArcs) * ln(nbNoeuds))


                      [C++] Implémentation avec priority queue


                      /* Par The Doctor pour le sdz */
                      #include <cstdio>
                      #include <vector>
                      #include <queue>
                      
                      using namespace std;
                      
                      const int NB_MAX_NOEUDS = 1000;
                      const int INFINI = 1000*1000*1000;
                      const int INDEFINI = -INFINI;
                      
                      struct arrete
                      {
                         int idNoeudArrive;
                         int poidsArrete;
                         
                         arrete() {}
                         arrete(int idNoeudArrive, int poidsArrete) : idNoeudArrive(idNoeudArrive), poidsArrete(poidsArrete) {}
                      };
                      
                      struct noeud
                      {
                         int idNoeud;
                         int dist;
                         
                         noeud() {}
                         noeud(int idNoeud, int dist) : idNoeud(idNoeud), dist(dist) {}      
                         
                         bool operator < (const noeud &autre) const
                         {
                            if(dist != autre.dist)
                               return dist > autre.dist;
                            return idNoeud > autre.idNoeud;
                         }
                      };
                      
                      
                      vector<arrete> voisinDuNoeud[NB_MAX_NOEUDS+2];
                      priority_queue<noeud> noeudsAVisiter;
                      int dist[NB_MAX_NOEUDS+2];
                      bool dejaVu[NB_MAX_NOEUDS+2];
                      int chemin[NB_MAX_NOEUDS+2];
                      
                      int nbNoeuds, nbArretes, noeudDepart, noeudArrive;
                      
                      void initDist()
                      {
                         for(int iCase = 0 ; iCase < NB_MAX_NOEUDS+2 ; iCase++)
                            dist[iCase] = INDEFINI;
                      }
                      
                      int main()
                      {
                         scanf("%d%d%d%d", &nbNoeuds, &nbArretes, &noeudDepart, &noeudArrive);
                         initDist();
                         for(int iArrete = 0 ; iArrete < nbArretes ; iArrete++)
                         {
                            int noeud1, noeud2, poids;
                            scanf("%d%d%d", &noeud1, &noeud2, &poids);
                            voisinDuNoeud[noeud1].push_back(arrete(noeud2, poids));
                            voisinDuNoeud[noeud2].push_back(arrete(noeud1, poids));
                         }
                         
                         noeudsAVisiter.push(noeud(noeudDepart, 0));
                         
                         while(!noeudsAVisiter.empty())
                         {
                            int idNoeudEnCours = noeudsAVisiter.top().idNoeud;
                            int distNoeudEnCours = noeudsAVisiter.top().dist;
                            
                            noeudsAVisiter.pop();
                            if(!dejaVu[idNoeudEnCours])
                            {
                               dejaVu[idNoeudEnCours] = true;
                               dist[idNoeudEnCours] = distNoeudEnCours;
                               for(int iVoisin = 0 ; iVoisin < (int)voisinDuNoeud[idNoeudEnCours].size() ; iVoisin++)
                                  noeudsAVisiter.push(noeud(voisinDuNoeud[idNoeudEnCours][iVoisin].idNoeudArrive, dist[idNoeudEnCours]+voisinDuNoeud[idNoeudEnCours][iVoisin].poidsArrete));
                            }
                            
                         }
                         printf("Distance minimale %d -> %d : %d\n", noeudDepart, noeudArrive, dist[noeudArrive]);
                      }
                      


                      The Doctor
                      • Partager sur Facebook
                      • Partager sur Twitter
                        9 août 2011 à 15:31:56

                        Je ne sais pas si cela a été dit, mais pour le C++ l'entête <algorithm> offre déjà un bon panel d'algorithmes tout prêts -> http://en.cppreference.com/w/cpp/algorithm
                        • Partager sur Facebook
                        • Partager sur Twitter
                          29 janvier 2012 à 22:24:52

                          @The Doctor: on aurait aimé un peu plus d'encapsulation. Là tu exposes complètement la structure que tu utilises… c'est pas très joli-joli. Image utilisateur
                          • Partager sur Facebook
                          • Partager sur Twitter
                            30 janvier 2012 à 7:36:51

                            Je fais de l'algo moi, pas de la prog.
                            • Partager sur Facebook
                            • Partager sur Twitter
                              1 février 2012 à 8:44:59

                              (The Doctor, il n'y a qu'un seul r à arête)
                              • Partager sur Facebook
                              • Partager sur Twitter
                                1 février 2012 à 21:58:53

                                Oups, décidément je ne le retiendrai jamais.
                                • Partager sur Facebook
                                • Partager sur Twitter
                                  21 avril 2012 à 22:00:46

                                  Calcul du plus petit commun multiple de deux nombres entiers positifs : PPCM


                                  Principe


                                  Le PPCM (lowest common multiple LCM en anglais mais j'ai trouvé d'autres acronymes comme Integer Less Common Multiple) est utilisé dans différentes opérations, il permet notamment de réduire des fractions. Il est très utilisé dans les opérations de chiffrement.

                                  Le PPCM constitue le plus petit entier qui soit un multiple commun des nombres dont on recherche le PPCM. Ainsi le PPCM des nombres A et B divise tous les multiples communs de A et de B. Ainsi le PPCM de 12 est 10 est 60 (12 x 5 = 10 x 6 = 60).

                                  La plupart des algorithmes que j'ai trouvé ne traitent que deux nombres à la fois, celui que je vous propose peut en traiter davantage, je pense que la limite tant du nombre de nombres traités que de leur taille sera celle de votre processeur. J'ai choisi une méthode acceptée par mon ordinateur, les deux autres ne fonctionnaient pas dès lors que les nombres traités dépassaient quelques dizaines.


                                  Complexité


                                  Le PPCM s'enseigne en collège, l'algorithme ne me semble donc pas très compliqué.

                                  Il existe plusieurs méthodes pour extraire le PPCM. La première consiste à calculer de manière croissante les n premiers multiples de chaque nombre et de les comparer jusqu'à trouver le premier, donc le plus faible, multiple commun.

                                  La seconde nécessite de décomposer chaque nombre en produit de facteurs premiers puis de comparer cette décomposition.

                                  J'ai essayé ces deux premières méthodes mais si elles fonctionnent bien avec de petits nombres, je suis rapidement arrivé aux limites de capacité de mon ordinateur. Je me suis donc rabattu sur la troisième méthode qui nécessite de passer par le PGCD (plus grand commun diviseur : il s'agit du plus grand entier naturel par lequel chacun des nombres considérés puisse être divisé avec un reste égal à 0. Ainsi le PGCD de 12 et 10 est 2 : 12 /2 = 6, c'est un entier et 10/ 2 = 5, entier également. En anglais le PGCD se nomme greatest common divisor - GCD, integer greatest common factor ou highest common factor).

                                  En effet le calcul du PGCD de deux nombres par l'algorithme d'Euclide est assez rapide. Il consiste à effectuer des divisions en cascade, le plus grand des deux nombres est d'abord divisé par le plus petit puis le diviseur est divisé par le reste et ainsi de suite.

                                  Je suis parti de la formule qui veut que le produit du PPCM et du PGCD soit égal au produit des nombres, soit pour deux nombres A et B :

                                  <math>\(A x B = PPCM(A,B) x PGCD(A,B)\)</math>


                                  Par conséquent :
                                  <math>\(PPCM(A,B) = (A x B) / PGCD(A,B)\)</math>


                                  L'algorithme utilise le principe de la récursivité pour calculer le PPCM (et le PGCD), à partir d'une fonction qui calcule le PPCM de deux nombres. En effet :

                                  <math>\(PPCM(A,B,C) = PPCM (A, PPCM(B,C)\)</math>


                                  L'algorithme comporte plusieurs fonctions, la fonction PPCM(A,B) qui calcule le PPCM de deux nombres entiers positifs. Cette fonction fait appel à la fonction PGCD(A,B) qui calcule le PGCD de deux nombres entiers positifs. La fonction PPCM_TAB calcule le PPCM de plusieurs nombres saisis dans un tableau à une dimension. Ainsi pour calculer le PPCM de A,B,C et D il faudra passer un tableau (array(A,B,C,D)) en paramètre.

                                  Mon algorithme fait appel à des fonctions dont les caractéristiques se trouvent assez facilement sur internet, j'ai tout de même rencontré quelques difficultés à trouver des implémentations en PHP. Ma plus-value réside surtout dans les tests de sécurité (je vérifie que les paramètres sont bien des entiers non nuls) et dans la possibilité de traiter plusieurs nombres et pas seulement deux.

                                  Ma formation scientifique étant assez lointaine je ne sais pas si cet algorithme est particulièrement efficace ni sûr, ce qui est certain c'est qu'il m'a permis de résoudre mes problèmes de temps de traitement. On verra bien les remarques et améliorations qui seront proposées.


                                  [PHP] Implémentation



                                  function pgcd($nb1,$nb2)
                                  // ******************************************************************************
                                  // *                                                                            *
                                  // *  CALCULE LE PGCD DE DEUX ENTIERS POSITIFS    				*
                                  // *	@$nb1 : entier positif   						*
                                  // *	@$nb2 : entier positif   						*
                                  // *                                                                            *
                                  // ******************************************************************************
                                  {
                                  
                                   if (($nb1 > 0) AND ($nb2 > 0) AND is_integer($nb1) AND is_integer($nb2))
                                     // Vérification : $nb1 et $nb2 sont des entiers positifs
                                     {
                                       while(($nb1>1) AND ($nb2 != 0)) // Tant que le reste n'est pas nul et le 
                                                                       // diviseur supérieur à 1
                                        {
                                          $reste = $nb1%$nb2;    // division entière
                                  	$nb1=$nb2;
                                          $nb2=$reste;
                                      }
                                  	}
                                    else $nb1 = false; // en cas d'erreur la fonction renvoie false
                                    return $nb1; // retourne le résultat
                                  }
                                  //
                                  // ===============================================================================
                                  //
                                  
                                  function ppcm($nb1, $nb2)
                                  // ******************************************************************************
                                  // *                                                                            *
                                  // *  CALCULE LE PPCM DE DEUX ENTIERS POSITIFS    				*
                                  // *	@$nb1 : entier positif   						*
                                  // *	@$nb2 : entier positif   						*
                                  // *                                                                            *
                                  // ******************************************************************************
                                  {
                                    if (is_integer($nb1) AND is_integer($nb1)) // Vérification : entiers
                                      {
                                        if (($nb1 > 0) AND ($nb2 >0)) // Vérification : entiers positifs
                                  	{
                                  	  $le_pgcd = pgcd($nb1, $nb2);
                                  	  if (is_integer($le_pgcd) AND is_integer($le_pgcd))
                                  	    $le_ppcm = ($nb1 * $nb2) / $le_pgcd;
                                  	  else $le_ppcm = $nb1 * $nb2;  // Sinon le ppcm est égal au produit des 2 nb
                                  	}
                                      }
                                    else $le_ppcm = false;  // en cas de problème la fonction renvoie faux
                                    return $le_ppcm;
                                  }
                                  //
                                  // ===============================================================================
                                  //
                                  function ppcm_tab($tab_nb)
                                  // ******************************************************************************
                                  // *                                                                            *
                                  // *  CALCULE LE PGCD DE PLUSIEURS ENTIERS POSITIFS    				*
                                  // *     @$tab_nb : tableau à une dimension d'entiers				*
                                  // *                                                                            // // ******************************************************************************
                                  {
                                  	sort($tab_nb); //  tri du tableau pour réinitialiser les clés
                                  	$taille = count($tab_nb);
                                  	$retour = false;
                                  	if (is_array($tab_nb))
                                  	  {	//	if 1
                                  	    switch ($taille)
                                  	      {	// switch 1
                                  		case 1 : 
                                  	          if (is_integer($tab_nb[0]) AND ($tab_nb[0] != 0)) $retour = $tab_nb[0]; // s'il n'y a qu'un seul nombre, c'est lui le PPCM !
                                  		break;
                                  		case 2 :
                                  		  if (is_integer($tab_nb[0]) AND is_integer($tab_nb[1]) AND ($tab_nb[0] != 0) AND ($tab_nb[1] != 0)) $retour = ppcm($tab_nb[0], $tab_nb[1]);
                                                      // s'il y a deux nombres, après avoir vérifié signe et type, c'est simple, on appelle la fonction PPCM
                                  		break;			
                                  		default :
                                  		  if (is_integer($tab_nb[0]) AND ($tab_nb[0] != 0)) 
                                  		    {
                                  			$tab_reduit = array_slice($tab_nb, 1);
                                  			$retour = ppcm($tab_nb[0],ppcm_tab($tab_reduit));
                                  		    }
                                                         // s'il y a plusieurs nombres, on construit un tableau qui compte une case en moins (la première) et on appelle récursivement la fonction sur le nouveau tableau.
                                  
                                  	       }	// switch 1
                                              }	// if 1
                                  	return $retour;
                                  }
                                  

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    28 août 2012 à 15:39:44

                                    Un tri par sélection en Haskell :
                                    selTri2 [] = []
                                    selTri2 l = x:(selTri2 xs)
                                      where (x,xs) = remMin l
                                     
                                    remMin [e] = (e, [])
                                    remMin (x:xs) | x < y = (x,y:ys) -- L'ordre n'a pas besoin d'être conservé
                                                  | otherwise = (y,x:ys)
                                      where (y,ys) = remMin xs
                                    


                                    Crible d'Ératosthène en Haskell, repris de Literate Programs (merci à Cygal) :

                                    sans (x:xs) (y:ys) | (x < y)   = x:    xs  `sans` (y:ys)
                                                       | (x == y)  =       xs  `sans`    ys
                                                       | otherwise =    (x:xs) `sans`    ys
                                    sans l _ = l
                                    
                                    union (x:xs) (y:ys) | (x < y)   = x: union    xs  (y:ys)
                                                        | (x == y)  = x: union    xs     ys
                                                        | otherwise = y: union (x:xs)    ys
                                    union xs ys = xs ++ ys
                                    
                                    premiers    = 2:3: ([5,7..] `sans` nonPremiers)
                                    nonPremiers = foldr f [] [ [p*p, p*(p+2) ..] | p <- tail premiers]
                                      where f (x:xs) ys = x: union xs ys
                                    


                                    J'ai mis foldr à la place de foldr1 car l'évaluation de f semble ne pas être paresseuse (a besoin de ys, donc va chercher dans premiers, etc., alors qu'avoir le premier élément, x, suffirait, mais non...).

                                    Une autre version du tri rapide en Haskell plus longe mais plus rapide que celle-ci :

                                    qSort' [] = []
                                    qSort' (x:xs) =  (qSort inf) ++ eq ++ (qSort sup)
                                      where (inf,eq,sup) = partitionner x xs ([],[x],[])
                                    
                                    partitionner _ [] classement = classement
                                    partitionner m (x:xs) (i,e,s) = partitionner m xs nClassement
                                      where nClassement | x < m     = (x:i,e,s)
                                                        | x == m    = (i,x:e,s)
                                                        | otherwise = (i,e,x:s)
                                    


                                    On pourrait tirer un pivot au hasard, mais je pense que ça n'améliorerait pas forcément la rapidité, à moins qu'on fasse avec des tableaux et pas des listes.

                                    PS : j'avais commencé à faire une recherche dichotomique sur des listes en Haskell... hem...
                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                      28 août 2012 à 16:29:38

                                      Citation : Idéophage

                                      Crible d'Ératosthène en Haskell :

                                      premiers = enleverMult [2..]
                                        where enleverMult (x:xs) =
                                                x: enleverMult (filter (\n -> (mod n x) /= 0) xs)
                                      


                                      Malheureusement, ce n'est pas un crible d'Ératosthène : l'idée du crible est d'enlever une fois pour toutes les multiples des nombres premiers déjà vus, ce qui n'est pas fait ici, et ce qui donne un programme très lent (cf. [1] et [2]). Par ailleurs, une list comprehension aurait été peut-être plus élégante ici : [x | x <- xs, n `mod` x /= 0].

                                      [1] Sieve of Eratosthenes (Haskell) - LiteratePrograms
                                      [2] The Genuine Sieve of Eratosthenes
                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        28 août 2012 à 16:50:05

                                        Juste un petit message pour dire que j'ai édité le premier post en répertoriant vos codes qui se sont accumulés depuis plus d'un an. Merci à vous. :)
                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                          28 août 2012 à 18:53:01

                                          Chaînes de caractères : Distance de Leveinshtein − version récursive



                                          La distance de Leveinshtein sert à mesurer la similarité entre deux mots. Sa définition est déjà présentée ici.


                                          Principe



                                          Citation : Wikipedia

                                          La distance de Levenshtein mesure la similarité entre deux chaînes de caractères. Elle est égale au nombre minimal de caractères qu'il faut supprimer, insérer ou remplacer pour passer d’une chaîne à l’autre.


                                          Par exemple, « MOT » et « MORT » ont une distance de 1, car il suffit d’ajouter une lettre pour passer du premier au second. « CANARD » et « RENARD » ont une distance de 2 (remplacement de deux lettres), « POULE » et « TOUX » ont une distance de 3 (remplacement de deux lettres et suppression d’une troisième).

                                          Plus cette distance est faible, plus les mots sont similaires.


                                          Complexité


                                          Temporelle : <math>\(O(3^d)\)</math>, où <math>\(d\)</math> est le nombre de différences (la distance recherchée, quoi). Niveau performances, ce n’est donc pas terrible du tout.
                                          Spatiale : <math>\(O(d)\)</math>.


                                          [C] Implémentation



                                          Il s’agit ici de présenter une variante récursive de l’algorithme de calcul de la distance de Leveinshtein présenté ici, peut-être plus simple à comprendre, et qui pourrait consommer moins de mémoire puisqu’il n’y a pas besoin de tenir à jour un tableau supplémentaire. Ce dernier point n’est toutefois pas évident car la consommation mémoire de cet algorithme est cachée par la récursivité (pile d’appel), mais existe bel et bien.

                                          #include <string.h>
                                          
                                          
                                          unsigned min(unsigned a, unsigned b) {
                                              return (a < b) ? a : b;
                                          }
                                          
                                          
                                          unsigned distance(const char* s, const char* t) {
                                              /* Aller jusqu’à la première différence. */
                                              for(;  *s == *t;  s++, t++) {
                                                  if(!*s)    // Si les chaînes sont égales, leur distance est 0.
                                                      return 0;
                                              }
                                          	
                                              if(!*s)    return strlen(t);
                                              if(!*t)    return strlen(s);
                                              
                                              /* Calcule la distance minimale entre les chaînes restantes selon les
                                                 trois cas de modification possibles : */
                                              unsigned d = distance(s+1, t  );      /* suppression d’un caractère, */
                                               d = min( d, distance(s,   t+1) );    /* insertion d’un caractère, */
                                               d = min( d, distance(s+1, t+1) );    /* remplacement d’un caractère. */
                                              
                                              /* la distance est 1 (la différence trouvée) plus la distance minimale
                                                 entre les fins de chaînes. */
                                              return 1 + d;
                                          }
                                          

                                          En pratique, cet algorithme sera inefficace s’il y a trop de différences entre les chaînes, car on a vu que sa complexité est exponentielle : <math>\(O(3^d)\)</math> (dans le pire des cas, les chaînes sont complètement différentes et on a du <math>\(O(3^n)\)</math>, où <math>\(n\)</math> est la longueur de la plus grande chaîne). Cette complexité vient du fait qu’à chaque fois que l’on rencontre une différence, on s’appelle 3 fois par récurrence, et ainsi on multiplie par 3 les opérations effectuées. On pourrait l’optimiser légèrement en n’appelant pas à nouveau distance si un appel précédent nous a donné 0 (car ce sera forcément le minimum).

                                          Cependant, cet algorithme peut facilement être adapté non plus pour calculer la distance exacte entre deux chaînes, mais pour les comparer en disant si leur distance est inférieure à une limite prédéfinie :
                                          /* Retourne vrai si la distance entre les deux chaînes est inférieure
                                             ou égale à d, faux sinon. */
                                          bool compareDistance(const char* s, const char* t, unsigned d) {
                                              for(;  *s == *t;  s++, t++) {
                                                  if(!*s)    // Les chaînes sont égales.
                                                      return true;
                                              }
                                              if(!d)    // Il y a une différence alors qu’on n’en accepte pas.
                                                  return false;
                                              
                                              /* s et t pointent maintenant sur le caractère différent, S et T sur le
                                                 suivant (si la fin de la chaîne n’est pas déjà atteinte − sans cette
                                                 sécurité, on pourrait lire après la fin) */
                                              const char *S = s,  *T = t;
                                              if(*s)    S++;
                                              if(*t)    T++;
                                              
                                              /* Compare la suite selon les trois cas de modification possibles : */
                                              return compareDistance(S, t, d-1)     /* suppression d’un caractère, */
                                                  || compareDistance(s, T, d-1)     /* insertion d’un caractère, */
                                                  || compareDistance(S, T, d-1);    /* remplacement d’un caractère. */
                                          }
                                          

                                          Ce code est plus efficace que le précédent car il s’arrête dès que l’on a dépassé la limite souhaitée. On n’a pas la distance exacte, mais l’on sait si nos deux chaînes sont suffisamment similaires à notre goût, ce qui est souvent ce que l’on souhaite savoir au final. De plus, il tire parti de l’évaluation en court-circuit du OU logique (||) : si un appel à compareDistance a déjà renvoyé true, les appels suivants ne sont pas effectués car on sait déjà que le OU vaudra true.


                                          Édité d’après les remarques de snlsdflkkl.
                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                            29 août 2012 à 18:54:58

                                            Combinatoire : ième permutation d’une liste à n éléments − version récursive



                                            Prenons une liste d’éléments ordonnés ABCD.
                                            Sa première permutation est ABCD, sa deuxième ABDC, sa troisième ACBD, etc. L’ordre des permutations est déterminé par la valeur des éléments (ici, c’est l’ordre alphabétique) : initialement dans l’ordre croissant, la dernière permutation les donne dans l’ordre décroissant.

                                            Le but de l’algorithme présenté ici est d’obtenir la <math>\(i\)</math>ème permutation d’une liste de <math>\(n\)</math> éléments, sans calculer les permutations précédentes. Il est déjà expliqué ici. Il s’agit ici d’en présenter une variante récursive et de fournir des explications plus détaillées.


                                            Principe



                                            À partir d’ici, les indices (<math>\(i\)</math>, <math>\(j\)</math> et <math>\(k\)</math>) partiront de zéro, comme toujours en informatique.

                                            On note tout d’abord que la <math>\(i\)</math>ème permutation d’une liste à <math>\(n\)</math> éléments, est la <math>\(i\)</math>ème permutation de <math>\(n\)</math> éléments parmi <math>\(n\)</math>. Brillant.

                                            On remarque ensuite que la <math>\(i\)</math>ème permutation de <math>\(p\)</math> éléments parmi <math>\(n\)</math>, correspond à la <math>\(j\)</math>ème permutation de <math>\(p-1\)</math> éléments parmi les <math>\(n\)</math>, suivie du <math>\(k\)</math>ème élément (par ordre croissant) parmi les <math>\(n-p+1\)</math> restants. On a donc une relation de récurrence. Certes, mais que valent <math>\(j\)</math> et <math>\(k\)</math> ?

                                            Il existe <math>\(n-p+1\)</math> permutations de <math>\(p\)</math> éléments parmi <math>\(n\)</math> qui commencent par la même « sous-permutation » de <math>\(p-1\)</math> éléments (car il nous faut rajouter un élément en dernière position, et pour celui-ci on a le choix entre les <math>\(n-p+1\)</math> éléments restants). On en conclut que <math>\(j\)</math> est le quotient de la division de <math>\(i\)</math> par <math>\(n-p+1\)</math>, et <math>\(k\)</math> en est le reste (souvenez-vous que les indices commencent à zéro).
                                            La factorielle est bien présente dans cet algorithme, mais « cachée » dans la récurrence : au fur et à mesure de la récurrence, on divise <math>\(i\)</math> par <math>\(n\)</math>, puis par <math>\(n-1\)</math>, puis par <math>\(n-2\)</math>, etc. C’est donc plus efficace que de la calculer itérativement pour tout <math>\(p\)</math>.



                                            Complexité


                                            La complexité est de <math>\(O(n)\)</math>, où <math>\(n\)</math> est le nombre d’éléments de la liste, puisqu’on opère par récurrence sur chacune des <math>\(n\)</math> positions. Elle ne dépend donc pas de <math>\(i\)</math>, ce qui signifie que cet algorithme peut nous donner la millionième permutation aussi rapidement que la première.


                                            [C] Implémentation



                                            Ici, la permutation est effectuée sur un tableau de char et en place (c’est-à-dire qu’elle ne nécessite pas de création de données supplémentaires).

                                            #include <string.h>    /* memmove */
                                            
                                            
                                            
                                            /* i-ème permutation de p éléments parmi n (i partant de 0). */
                                            void ithPermOfPInN(char set[], unsigned n, unsigned p, unsigned i)
                                            {
                                                /* Cas de base. */
                                                if(!p)
                                                    return;
                                                
                                                unsigned j = i / (n-p+1);    /* index de la permutation de p-1 parmi n */
                                                unsigned k = i % (n-p+1);    /* index de l’élément à choisir dans l’ensemble restant */
                                                
                                                /* 1. Récurrence : donne les p-1 premiers éléments de la permutation. */
                                                ithPermOfPInN(set, n, p-1, j);
                                                
                                                /* 2. Ajoute le p-ème élément après les p-1 déjà permutés. Les éléments
                                                   restants sont placés dans l’ordre après la permutation faite. */
                                                char e = set[p-1 + k];    /* élément à rajouter */
                                                  /* décalage pour ôter e de l’ensemble restant et l’insérer à sa gauche,
                                                     juste après la permutation déjà faite : */
                                                memmove(&set[p], &set[p-1], k);
                                                set[p-1] = e;             /* insertion de e */
                                            }
                                            
                                            
                                            
                                            /* i-ème permutation de n éléments (i partant de 0). */
                                            void ithPermOfN(char set[], unsigned n, unsigned i)
                                            {
                                                ithPermOfPInN(set, n, n, i);
                                            }
                                            


                                            Par exemple :
                                            #include <stdio.h>
                                            
                                            void exemple(void)
                                            {
                                                char perm[] = "ABCD";
                                                printf("   %s\n", perm);
                                                
                                                ithPermOfN(perm, 4, 6);
                                                printf("-> %s\n", perm);
                                            }
                                            

                                            La sortie est :
                                               ABCD
                                            -> BACD


                                            Notons au passage que cet algorithme n’échoue pas si on lui demande un indice de permutation plus grand que le nombre de permutations (<math>\(i\geq n!\)</math>). Il revient au point de départ. Ainsi, si on lui demande la 24ème permutation de ABCD, on obtiendra de nouveau ABCD. :magicien:
                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              30 août 2012 à 17:07:55

                                              Citation : Cygal

                                              Malheureusement, ce n'est pas un crible d'Ératosthène : l'idée du crible est d'enlever une fois pour toutes les multiples des nombres premiers déjà vus, ce qui n'est pas fait ici, et ce qui donne un programme très lent (cf. [1] et [2]).



                                              Effectivement, mon code teste pour chaque nombre s'il est divisible par un nombre premier inférieur. À vrai dire, je n'avais même pas imaginé l'algorithme dans ma tête, me contentant d'une définition. Je suis désolé, je n'aurais pas du poster du code Haskell alors que ça ne fait que très peu de temps que je connais...

                                              Citation : Cygal


                                              Une list comprehension aurait été peut-être plus élégante ici : [x | x <- xs, n `mod` x /= 0].



                                              Oui, tout à fait.

                                              Merci de ces remarques, je remplace mon code par celui présent sur Literate Programs.
                                              • Partager sur Facebook
                                              • Partager sur Twitter
                                                30 août 2012 à 19:12:00

                                                Arithmétique : décomposition en facteurs premiers − optimisations



                                                Le principe de la décomposition d’un entier en produits de facteurs premiers est déjà exposé ici. L’algorithme présenté peut cependant être optimisé de plusieurs façons.


                                                Principe



                                                On constate que tous les nombres premiers sauf 2 sont impairs (logique, les nombres pairs étant divisible par 2…). Plutôt que d’essayer de diviser par tous les nombres à partir de 2, on peut donc ne tester que les impairs, en traitant 2 séparément. Ainsi, on fait déjà deux fois moins de tests.
                                                On continuant ainsi, on pourrait aussi décider d’éliminer d’office les multiples de 3, de 5… mais plus on en « saute », plus cela devient compliqué à gérer et moins le gain sera intéressant. Ici, on n’ignorera que les pairs.

                                                De plus, <math>\(n\)</math> admet au plus un seul diviseur premier supérieur à <math>\(\sqrt n\)</math> (car si deux nombres sont supérieurs à <math>\(\sqrt n\)</math>, leur produit sera forcément supérieur à <math>\(n\)</math>). Si le nombre testé devient plus grand que <math>\(\sqrt n\)</math>, on peut donc en déduire que le dernier diviseur est <math>\(n\)</math> et arrêter la boucle. Cela réduit encore les opérations nécessaires (mais d’un autre côté, le calcul de la racine carrée a un coût non négligeable, cette optimisation est donc intéressante pour factoriser de grands nombres).


                                                Complexité



                                                De l’ordre de <math>\(O(\sqrt n)\)</math>.


                                                [C] Implémentation



                                                Pour simplifier, ici, on se contentera d’afficher les facteurs premiers au fur et à mesure qu’on les trouve, sans les stocker.

                                                #include <stdio.h>
                                                #include <math.h>
                                                
                                                
                                                void facteursPremiers(unsigned n) {
                                                    unsigned p, racine;
                                                    
                                                    while(n%2 == 0) {
                                                        printf("2 ");
                                                        n /= 2;
                                                    }
                                                    
                                                    p = 3;
                                                    racine = sqrt(n);
                                                    
                                                    while(n > 1) {
                                                        if(n%p == 0) {
                                                            printf("%u ", p);
                                                            n /= p;
                                                            racine = sqrt(n);
                                                        }
                                                        else {
                                                            p += 2;
                                                            if(p > racine)
                                                                p = n;
                                                        }
                                                    }
                                                }
                                                
                                                
                                                /* En rusant, on peut aussi éviter de rajouter du code
                                                   pour traiter 2 : */
                                                void facteursPremiers(unsigned n) {
                                                    unsigned p, racine;
                                                    
                                                    p = 2;
                                                    racine = sqrt(n);
                                                    
                                                    while(n > 1) {
                                                        if(n%p == 0) {
                                                            printf("%u ", p);
                                                            n /= p;
                                                            racine = sqrt(n);
                                                        }
                                                        else {
                                                            p += 1 + (p%2);
                                                            if(p > racine)
                                                                p = n;
                                                        }
                                                    }
                                                }
                                                


                                                Par exemple :
                                                facteursPremiers(308)
                                                

                                                La sortie est :
                                                2 2 7 11
                                                • Partager sur Facebook
                                                • Partager sur Twitter
                                                  1 septembre 2012 à 15:25:06

                                                  Salut,

                                                  Il me semble que si tu arrête ta boucle lorsque p > racine, ca t'évite de la recalculer a chaque fois.
                                                  Il y a aussi une astuce pour remplacer p%2 : si je me souviens bien c'est !(p & 1). Le modulo étant coûteux, ca peut aider.

                                                  Hedi
                                                  • Partager sur Facebook
                                                  • Partager sur Twitter
                                                    1 septembre 2012 à 15:31:51

                                                    Citation : hedi07

                                                    Il me semble que si tu arrête ta boucle lorsque p > racine, ca t'évite de la recalculer a chaque fois.


                                                    C’est le cas. On fait encore un tour avec p = n, mais on s’arrête juste après. Ça évite de répéter le code lorsqu’on trouve un facteur.

                                                    Citation : hedi07

                                                    Il y a aussi une astuce pour remplacer p%2 : si je me souviens bien c'est !(p & 1). Le modulo étant coûteux, ca peut aider.



                                                    En effet (c’est p&1), mais le compilateur fait cette optimisation lui-même, donc je privilégie le sens et la clarté.
                                                    • Partager sur Facebook
                                                    • Partager sur Twitter
                                                      9 septembre 2012 à 20:47:24

                                                      Calculs divers : Exponentiation rapide



                                                      Comme son nom l’indique, l’algorithme d’exponentiation rapide permet de calculer une puissance (entière) d’un nombre, soit <math>\(b^n\)</math>. Il peut cependant être généralisé à d’autres opérations, où le résultat est la « répétition » <math>\(n\)</math> fois de la base <math>\(b\)</math> (qui n’est pas forcément un nombre), selon une opération donnée.
                                                      Son intérêt par rapport à l’algorithme naïf, en <math>\(O(n)\)</math> (on répète bêtement <math>\(n\)</math> fois l’opération), est une meilleure complexité. Cependant, en pratique, vu la vitesse des ordinateurs actuels, le gain pour calculer une puissance d’un nombre entier est insignifiant ; mais l’algorithme est intéressant, et le gain peut être plus concret dans d’autres applications.


                                                      Principe



                                                      Une façon de l’appréhender est de considérer l’écriture binaire de <math>\(n\)</math>.

                                                      Par exemple, si <math>\(n = 13\)</math>, on a <math>\(n = 2^3 + 2^2 + 2^0\)</math> (en binaire, <math>\(n = 0b1101\)</math>). Dans ce cas :
                                                      <math>\(b^n = b^{2^3 + 2^2 + 2^0} = b^{(2^3)} b^{(2^2)} b^{(2^0)}\)</math>

                                                      Conclusion : on peut calculer <math>\(b^n\)</math> en « lisant » l’écriture binaire de <math>\(n\)</math> (de droite à gauche) et en élevant la variable <math>\(b\)</math> au carré à chaque chiffre. Lorsqu’on rencontre un chiffre 1, on multiplie la valeur d’une variable « accumulateur » (qui stocke le résultat) avec la valeur actuelle de <math>\(b\)</math>. À la fin, l’accumulateur contient le résultat.

                                                      Dans notre exemple :
                                                      n = 0b1101
                                                      b = 3
                                                      
                                                      acc = 1
                                                      
                                                      Le premier chiffre de n en binaire est 1.
                                                          acc =  acc × b    // acc vaut maintenant 3
                                                      b =  b × b            // b vaut maintenant 3²
                                                      
                                                      Le chiffre suivant est 0.
                                                      b =  b × b            // b vaut maintenant 3⁴
                                                      
                                                      Le chiffre suivant est 1.
                                                          acc =  acc × b    // acc vaut maintenant 3×3⁴ = 3⁵
                                                      b =  b × b            // b vaut maintenant 3⁸
                                                      
                                                      Le chiffre suivant est 1.
                                                          acc =  acc × b    // acc vaut maintenant 3⁵×3⁸ = 3¹³
                                                      b =  b × b            // b vaut maintenant 3¹⁶ (mais aucune importance)

                                                      Comme on voit, à la fin de l’algorithme, l’accumulateur vaut bien <math>\(3^{13}\)</math>. :)

                                                      Cet algorithme correspond en fait exactement à la méthode « Diviser pour régner », souvent envisagée sous un angle récursif. L’avoir décrit ainsi va nous permettre de l’écrire également dans sa version itérative.


                                                      Complexité



                                                      La complexité temporelle est en <math>\(O(\log(n))\)</math>, car on itère pour chaque chiffre binaire de <math>\(n\)</math> (logarithme en base 2).


                                                      [C] Implémentation



                                                      Une première implémentation en C (ici pour la puissance d’un entier de type int).

                                                      /* Version itérative (préférable en C) */
                                                      int power(int x, unsigned n) {
                                                          int acc;
                                                          
                                                          for(acc = 1;  n;  n /= 2) {
                                                              if(n % 2)
                                                                  acc *= x;
                                                              x *= x;
                                                          }
                                                          
                                                          return acc;
                                                      }
                                                      
                                                      /* Version récursive (le « Diviser pour régner ») */
                                                      int power(int x, unsigned n) {
                                                          if(n) {
                                                              int acc = (n%2) ? x : 1;
                                                              return acc * power(x*x, n/2);
                                                          }
                                                          else
                                                              return 1;
                                                      }
                                                      


                                                      [OCaml] Implémentation



                                                      La même chose en OCaml (cette fois en récursion terminale).
                                                      let pow =
                                                          let rec pow_rec acc b = function
                                                              | 0  -> acc
                                                              | n  ->
                                                                  let acc = if (n mod 2 = 1) then acc*b else acc  in
                                                                  pow_rec acc (b*b) (n/2)
                                                          in
                                                          pow_rec 1
                                                      ;;
                                                      
                                                      pow 3 5;;
                                                      (*  - : int = 243  *)
                                                      


                                                      Bonus ! Le paradigme fonctionnel d’OCaml nous permet de généraliser facilement l’algorithme…
                                                      let rec gpow ( * ) acc b = function
                                                          | 0  -> acc
                                                          | n  ->
                                                              let acc = if (n mod 2 = 1) then acc*b else acc  in
                                                              gpow ( * ) acc (b*b) (n/2)
                                                      ;;
                                                      
                                                      (* Calcule la puissance d’un flottant. *)
                                                      let pow = gpow ( *. ) 1.;;
                                                      
                                                      (* Multiplie deux entiers. *)
                                                      let mul = gpow (+) 0;;
                                                      
                                                      (* Génère une liste de n éléments identiques. *)
                                                      let generate_list e = gpow (@) [] [e];;
                                                      
                                                      (* Concatène une chaîne n fois.  *)
                                                      let repeat_string = gpow (^) "";;
                                                      
                                                      pow 2.718 5;;
                                                      (*  - : float = 148.336238491865572  *)
                                                      mul 7 6;;
                                                      (*  - : int = 42  *)
                                                      generate_list 'z' 7;;
                                                      (*  - : char list = ['z'; 'z'; 'z'; 'z'; 'z'; 'z'; 'z']  *)
                                                      repeat_string "hi-"  5;;
                                                      (*  - : string = "hi-hi-hi-hi-hi-"  *)
                                                      
                                                      • Partager sur Facebook
                                                      • Partager sur Twitter
                                                      Anonyme
                                                        8 novembre 2012 à 2:36:25

                                                        Fonctions mathématiques : calcul de l'exponentielle


                                                        Principe


                                                        La méthode utilise le développement limité : <math>\(e^x = 1 + x + \frac{x^2}{2!} + \frac{x^3}{3!} + \ldots + \frac{x^n}{n!} + o(x\n)\)</math>.

                                                        [C] Implémentation



                                                        double exp(double x)
                                                        {
                                                            double res = 1 + x;
                                                            double puis = x, fact = 1;
                                                            const int pres = 25;
                                                            int i;
                                                        
                                                        /* Comme res vaut déjà 1 + x, il faut commencer à x^2, soit i = 2 */
                                                            for(i = 2; i < pres; i++)
                                                            {
                                                                puis *= x;
                                                                fact *= i;
                                                                res += puis / fact;
                                                            }
                                                            return res;
                                                        }
                                                        


                                                        La variable pres équivaut à <math>\(n\)</math> dans la formule. Il est inutile de trop l'augmenter.
                                                        • Partager sur Facebook
                                                        • Partager sur Twitter
                                                          8 novembre 2012 à 21:48:03

                                                          Citation : informaticienzero

                                                          La méthode utilise le développement limité : <math>\(e^x = 1 + x + \frac{x^2}{2!} + \frac{x^3}{3!} + \ldots + \frac{x^n}{n!} + o(x\n)\)</math> ce qui donne en abrégé <math>\(e^x = \sum_{n = 0}^\infty \frac{x^n}{n!}}\)</math>


                                                          Le développement limité n'a pas sa place ici. Ce que tu écris en "pas abrégé" et "abrégé" est tout à fait différent. À gauche le DL (c'est du x^n dans le o), valable au voisinage de 0 (car ce que tu fais est un DL en 0) et à droite le développement en série entière valable sur tout intervalle.

                                                          D'ailleurs, ça se voit pour des nombres un peu éloignés de 0 :

                                                          >>> import math
                                                          >>> exp = math.exp
                                                          >>> exp(300)
                                                          1.9424263952412558e+130
                                                          >>> def fact(n): return 1 if n == 0 else f*fact(n-1)
                                                          >>> sum(300**n/fact(n) for n in range(25))
                                                          4.946304776354304e+35
                                                          


                                                          En fait, il te manque un terme d'erreur. Il est donné par un reste (reste intégral) : http://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Taylor
                                                          • Partager sur Facebook
                                                          • Partager sur Twitter

                                                          Algorithmes divers multi-langage

                                                          × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                                          × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                                          • Editeur
                                                          • Markdown